mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-27 09:08:13 +08:00
merge
This commit is contained in:
+5
-11
@@ -12,20 +12,17 @@ license = "Apache-2.0"
|
||||
|
||||
[features]
|
||||
default = ["wasm"]
|
||||
wasm = ["wasm-bindgen", "graphene-std/wasm", "wasm-bindgen-futures"]
|
||||
wasm = ["wasm-bindgen", "graphene-std/wasm"]
|
||||
gpu = ["interpreted-executor/gpu", "wgpu-executor"]
|
||||
tauri = ["ron", "decouple-execution"]
|
||||
decouple-execution = []
|
||||
resvg = ["graphene-std/resvg"]
|
||||
vello = ["graphene-std/vello", "resvg"]
|
||||
ron = ["dep:ron"]
|
||||
|
||||
[dependencies]
|
||||
# Local dependencies
|
||||
graphite-proc-macros = { workspace = true }
|
||||
graph-craft = { workspace = true }
|
||||
interpreted-executor = { workspace = true }
|
||||
graphene-std = { workspace = true }
|
||||
graphene-std = { workspace = true } # NOTE: `graphene-core` should not be added here because `graphene-std` re-exports its contents
|
||||
preprocessor = { workspace = true }
|
||||
|
||||
# Workspace dependencies
|
||||
@@ -35,7 +32,6 @@ bitflags = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
bezier-rs = { workspace = true }
|
||||
kurbo = { workspace = true }
|
||||
futures = { workspace = true }
|
||||
glam = { workspace = true }
|
||||
@@ -46,17 +42,15 @@ num_enum = { workspace = true }
|
||||
usvg = { workspace = true }
|
||||
once_cell = { workspace = true }
|
||||
web-sys = { workspace = true }
|
||||
|
||||
# Required dependencies
|
||||
spin = "0.9.8"
|
||||
vello = { workspace = true }
|
||||
base64 = { workspace = true }
|
||||
spin = { workspace = true }
|
||||
|
||||
# Optional local dependencies
|
||||
wgpu-executor = { workspace = true, optional = true }
|
||||
|
||||
# Optional workspace dependencies
|
||||
wasm-bindgen = { workspace = true, optional = true }
|
||||
wasm-bindgen-futures = { workspace = true, optional = true }
|
||||
ron = { workspace = true, optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
# Workspace dependencies
|
||||
|
||||
@@ -106,6 +106,7 @@ pub const HIDE_HANDLE_DISTANCE: f64 = 3.;
|
||||
pub const HANDLE_ROTATE_SNAP_ANGLE: f64 = 15.;
|
||||
pub const SEGMENT_INSERTION_DISTANCE: f64 = 5.;
|
||||
pub const SEGMENT_OVERLAY_SIZE: f64 = 10.;
|
||||
pub const SEGMENT_SELECTED_THICKNESS: f64 = 3.;
|
||||
pub const HANDLE_LENGTH_FACTOR: f64 = 0.5;
|
||||
|
||||
// PEN TOOL
|
||||
@@ -126,6 +127,9 @@ pub const POINT_RADIUS_HANDLE_SNAP_THRESHOLD: f64 = 8.;
|
||||
pub const POINT_RADIUS_HANDLE_SEGMENT_THRESHOLD: f64 = 7.9;
|
||||
pub const NUMBER_OF_POINTS_DIAL_SPOKE_EXTENSION: f64 = 1.2;
|
||||
pub const NUMBER_OF_POINTS_DIAL_SPOKE_LENGTH: f64 = 10.;
|
||||
pub const ARC_SNAP_THRESHOLD: f64 = 5.;
|
||||
pub const ARC_SWEEP_GIZMO_RADIUS: f64 = 14.;
|
||||
pub const ARC_SWEEP_GIZMO_TEXT_HEIGHT: f64 = 12.;
|
||||
pub const GIZMO_HIDE_THRESHOLD: f64 = 20.;
|
||||
|
||||
// SCROLLBARS
|
||||
@@ -146,10 +150,10 @@ pub const COLOR_OVERLAY_WHITE: &str = "#ffffff";
|
||||
pub const COLOR_OVERLAY_BLACK_75: &str = "#000000bf";
|
||||
|
||||
// DOCUMENT
|
||||
pub const FILE_EXTENSION: &str = "graphite";
|
||||
pub const DEFAULT_DOCUMENT_NAME: &str = "Untitled Document";
|
||||
pub const FILE_SAVE_SUFFIX: &str = ".graphite";
|
||||
pub const MAX_UNDO_HISTORY_LEN: usize = 100; // TODO: Add this to user preferences
|
||||
pub const AUTO_SAVE_TIMEOUT_SECONDS: u64 = 15;
|
||||
pub const AUTO_SAVE_TIMEOUT_SECONDS: u64 = 1;
|
||||
|
||||
// INPUT
|
||||
pub const DOUBLE_CLICK_MILLISECONDS: u64 = 500;
|
||||
|
||||
+25
-49
@@ -1,11 +1,11 @@
|
||||
use crate::messages::debug::utility_types::MessageLoggingVerbosity;
|
||||
use crate::messages::defer::DeferMessageContext;
|
||||
use crate::messages::dialog::DialogMessageContext;
|
||||
use crate::messages::layout::layout_message_handler::LayoutMessageContext;
|
||||
use crate::messages::prelude::*;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Dispatcher {
|
||||
buffered_queue: Option<Vec<VecDeque<Message>>>,
|
||||
message_queues: Vec<VecDeque<Message>>,
|
||||
pub responses: Vec<FrontendMessage>,
|
||||
pub message_handlers: DispatcherMessageHandlers,
|
||||
@@ -14,8 +14,10 @@ pub struct Dispatcher {
|
||||
#[derive(Debug, Default)]
|
||||
pub struct DispatcherMessageHandlers {
|
||||
animation_message_handler: AnimationMessageHandler,
|
||||
app_window_message_handler: AppWindowMessageHandler,
|
||||
broadcast_message_handler: BroadcastMessageHandler,
|
||||
debug_message_handler: DebugMessageHandler,
|
||||
defer_message_handler: DeferMessageHandler,
|
||||
dialog_message_handler: DialogMessageHandler,
|
||||
globals_message_handler: GlobalsMessageHandler,
|
||||
input_preprocessor_message_handler: InputPreprocessorMessageHandler,
|
||||
@@ -24,7 +26,6 @@ pub struct DispatcherMessageHandlers {
|
||||
pub portfolio_message_handler: PortfolioMessageHandler,
|
||||
preferences_message_handler: PreferencesMessageHandler,
|
||||
tool_message_handler: ToolMessageHandler,
|
||||
workspace_message_handler: WorkspaceMessageHandler,
|
||||
}
|
||||
|
||||
impl DispatcherMessageHandlers {
|
||||
@@ -50,7 +51,10 @@ const SIDE_EFFECT_FREE_MESSAGES: &[MessageDiscriminant] = &[
|
||||
MessageDiscriminant::Frontend(FrontendMessageDiscriminant::UpdateDocumentLayerStructure),
|
||||
MessageDiscriminant::Frontend(FrontendMessageDiscriminant::TriggerFontLoad),
|
||||
];
|
||||
const DEBUG_MESSAGE_BLOCK_LIST: &[MessageDiscriminant] = &[MessageDiscriminant::Broadcast(BroadcastMessageDiscriminant::TriggerEvent(BroadcastEventDiscriminant::AnimationFrame))];
|
||||
const DEBUG_MESSAGE_BLOCK_LIST: &[MessageDiscriminant] = &[
|
||||
MessageDiscriminant::Broadcast(BroadcastMessageDiscriminant::TriggerEvent(EventMessageDiscriminant::AnimationFrame)),
|
||||
MessageDiscriminant::Animation(AnimationMessageDiscriminant::IncrementFrameCounter),
|
||||
];
|
||||
// TODO: Find a way to combine these with the list above. We use strings for now since these are the standard variant names used by multiple messages. But having these also type-checked would be best.
|
||||
const DEBUG_MESSAGE_ENDING_BLOCK_LIST: &[&str] = &["PointerMove", "PointerOutsideViewport", "Overlays", "Draw", "CurrentTime", "Time"];
|
||||
|
||||
@@ -90,14 +94,6 @@ impl Dispatcher {
|
||||
|
||||
pub fn handle_message<T: Into<Message>>(&mut self, message: T, process_after_all_current: bool) {
|
||||
let message = message.into();
|
||||
// Add all additional messages to the buffer if it exists (except from the end buffer message)
|
||||
if !matches!(message, Message::EndBuffer { .. }) {
|
||||
if let Some(buffered_queue) = &mut self.buffered_queue {
|
||||
Self::schedule_execution(buffered_queue, true, [message]);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// If we are not maintaining the buffer, simply add to the current queue
|
||||
Self::schedule_execution(&mut self.message_queues, process_after_all_current, [message]);
|
||||
@@ -129,14 +125,24 @@ impl Dispatcher {
|
||||
Message::Animation(message) => {
|
||||
self.message_handlers.animation_message_handler.process_message(message, &mut queue, ());
|
||||
}
|
||||
Message::AppWindow(message) => {
|
||||
self.message_handlers.app_window_message_handler.process_message(message, &mut queue, ());
|
||||
}
|
||||
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, ());
|
||||
}
|
||||
Message::Defer(message) => {
|
||||
let context = DeferMessageContext {
|
||||
portfolio: &self.message_handlers.portfolio_message_handler,
|
||||
};
|
||||
self.message_handlers.defer_message_handler.process_message(message, &mut queue, context);
|
||||
}
|
||||
Message::Dialog(message) => {
|
||||
let context = DialogMessageContext {
|
||||
portfolio: &self.message_handlers.portfolio_message_handler,
|
||||
preferences: &self.message_handlers.preferences_message_handler,
|
||||
viewport_bounds: &self.message_handlers.input_preprocessor_message_handler.viewport_bounds,
|
||||
};
|
||||
self.message_handlers.dialog_message_handler.process_message(message, &mut queue, context);
|
||||
}
|
||||
@@ -204,11 +210,14 @@ impl Dispatcher {
|
||||
self.message_handlers.preferences_message_handler.process_message(message, &mut queue, ());
|
||||
}
|
||||
Message::Tool(message) => {
|
||||
let document_id = self.message_handlers.portfolio_message_handler.active_document_id().unwrap();
|
||||
let Some(document) = self.message_handlers.portfolio_message_handler.documents.get_mut(&document_id) else {
|
||||
let Some(document_id) = self.message_handlers.portfolio_message_handler.active_document_id() else {
|
||||
warn!("Called ToolMessage without an active document.\nGot {message:?}");
|
||||
return;
|
||||
};
|
||||
let Some(document) = self.message_handlers.portfolio_message_handler.documents.get_mut(&document_id) else {
|
||||
warn!("Called ToolMessage with an invalid active document.\nGot {message:?}");
|
||||
return;
|
||||
};
|
||||
|
||||
let context = ToolMessageContext {
|
||||
document_id,
|
||||
@@ -221,44 +230,10 @@ impl Dispatcher {
|
||||
|
||||
self.message_handlers.tool_message_handler.process_message(message, &mut queue, context);
|
||||
}
|
||||
Message::Workspace(message) => {
|
||||
self.message_handlers.workspace_message_handler.process_message(message, &mut queue, ());
|
||||
}
|
||||
Message::NoOp => {}
|
||||
Message::Batched { messages } => {
|
||||
messages.iter().for_each(|message| self.handle_message(message.to_owned(), false));
|
||||
}
|
||||
Message::StartBuffer => {
|
||||
self.buffered_queue = Some(std::mem::take(&mut self.message_queues));
|
||||
}
|
||||
Message::EndBuffer { render_metadata } => {
|
||||
// Assign the message queue to the currently buffered queue
|
||||
if let Some(buffered_queue) = self.buffered_queue.take() {
|
||||
self.cleanup_queues(false);
|
||||
assert!(self.message_queues.is_empty(), "message queues are always empty when ending a buffer");
|
||||
self.message_queues = buffered_queue;
|
||||
};
|
||||
|
||||
let graphene_std::renderer::RenderMetadata {
|
||||
upstream_footprints: footprints,
|
||||
local_transforms,
|
||||
first_instance_source_id,
|
||||
click_targets,
|
||||
clip_targets,
|
||||
} = render_metadata;
|
||||
|
||||
// Run these update state messages immediately
|
||||
let messages = [
|
||||
DocumentMessage::UpdateUpstreamTransforms {
|
||||
upstream_footprints: footprints,
|
||||
local_transforms,
|
||||
first_instance_source_id,
|
||||
},
|
||||
DocumentMessage::UpdateClickTargets { click_targets },
|
||||
DocumentMessage::UpdateClipTargets { clip_targets },
|
||||
];
|
||||
Self::schedule_execution(&mut self.message_queues, false, messages.map(Message::from));
|
||||
}
|
||||
}
|
||||
|
||||
// If there are child messages, append the queue to the list of queues
|
||||
@@ -467,7 +442,7 @@ mod test {
|
||||
assert_eq!(layers_before_copy.len(), 3);
|
||||
assert_eq!(layers_after_copy.len(), 6);
|
||||
|
||||
println!("{:?} {:?}", layers_after_copy, layers_before_copy);
|
||||
println!("{layers_after_copy:?} {layers_before_copy:?}");
|
||||
|
||||
assert_eq!(layers_after_copy[5], shape_id);
|
||||
}
|
||||
@@ -522,7 +497,8 @@ mod test {
|
||||
);
|
||||
|
||||
let responses = editor.editor.handle_message(PortfolioMessage::OpenDocumentFile {
|
||||
document_name: document_name.into(),
|
||||
document_name: Some(document_name.to_string()),
|
||||
document_path: None,
|
||||
document_serialized_content,
|
||||
});
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
use crate::messages::prelude::*;
|
||||
|
||||
use super::animation_message_handler::AnimationTimeMode;
|
||||
use crate::messages::prelude::*;
|
||||
|
||||
#[impl_message(Message, Animation)]
|
||||
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
use crate::messages::prelude::*;
|
||||
|
||||
use super::app_window_message_handler::AppWindowPlatform;
|
||||
|
||||
#[impl_message(Message, AppWindow)]
|
||||
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
|
||||
pub enum AppWindowMessage {
|
||||
AppWindowMinimize,
|
||||
AppWindowMaximize,
|
||||
AppWindowUpdatePlatform { platform: AppWindowPlatform },
|
||||
AppWindowClose,
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
use crate::messages::app_window::AppWindowMessage;
|
||||
use crate::messages::prelude::*;
|
||||
use graphite_proc_macros::{ExtractField, message_handler_data};
|
||||
|
||||
#[derive(Debug, Clone, Default, ExtractField)]
|
||||
pub struct AppWindowMessageHandler {
|
||||
platform: AppWindowPlatform,
|
||||
maximized: bool,
|
||||
minimized: bool,
|
||||
}
|
||||
|
||||
#[message_handler_data]
|
||||
impl MessageHandler<AppWindowMessage, ()> for AppWindowMessageHandler {
|
||||
fn process_message(&mut self, message: AppWindowMessage, responses: &mut std::collections::VecDeque<Message>, _: ()) {
|
||||
match message {
|
||||
AppWindowMessage::AppWindowMaximize => {
|
||||
self.maximized = !self.maximized;
|
||||
responses.add(FrontendMessage::UpdateWindowState {
|
||||
maximized: self.maximized,
|
||||
minimized: self.minimized,
|
||||
});
|
||||
}
|
||||
AppWindowMessage::AppWindowMinimize => {
|
||||
self.minimized = !self.minimized;
|
||||
responses.add(FrontendMessage::UpdateWindowState {
|
||||
maximized: self.maximized,
|
||||
minimized: self.minimized,
|
||||
});
|
||||
}
|
||||
AppWindowMessage::AppWindowUpdatePlatform { platform } => {
|
||||
self.platform = platform;
|
||||
responses.add(FrontendMessage::UpdatePlatform { platform: self.platform });
|
||||
}
|
||||
AppWindowMessage::AppWindowClose => {
|
||||
responses.add(FrontendMessage::CloseWindow);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn actions(&self) -> ActionList {
|
||||
actions!(AppWindowMessageDiscriminant;)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Clone, Copy, Default, Debug, serde::Serialize, serde::Deserialize, specta::Type)]
|
||||
pub enum AppWindowPlatform {
|
||||
#[default]
|
||||
Web,
|
||||
Windows,
|
||||
Mac,
|
||||
Linux,
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
mod app_window_message;
|
||||
pub mod app_window_message_handler;
|
||||
|
||||
#[doc(inline)]
|
||||
pub use app_window_message::{AppWindowMessage, AppWindowMessageDiscriminant};
|
||||
#[doc(inline)]
|
||||
pub use app_window_message_handler::AppWindowMessageHandler;
|
||||
@@ -5,15 +5,15 @@ use crate::messages::prelude::*;
|
||||
pub enum BroadcastMessage {
|
||||
// Sub-messages
|
||||
#[child]
|
||||
TriggerEvent(BroadcastEvent),
|
||||
TriggerEvent(EventMessage),
|
||||
|
||||
// Messages
|
||||
SubscribeEvent {
|
||||
on: BroadcastEvent,
|
||||
on: EventMessage,
|
||||
send: Box<Message>,
|
||||
},
|
||||
UnsubscribeEvent {
|
||||
on: BroadcastEvent,
|
||||
message: Box<Message>,
|
||||
on: EventMessage,
|
||||
send: Box<Message>,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -2,7 +2,8 @@ use crate::messages::prelude::*;
|
||||
|
||||
#[derive(Debug, Clone, Default, ExtractField)]
|
||||
pub struct BroadcastMessageHandler {
|
||||
listeners: HashMap<BroadcastEvent, Vec<Message>>,
|
||||
event: EventMessageHandler,
|
||||
listeners: HashMap<EventMessage, Vec<Message>>,
|
||||
}
|
||||
|
||||
#[message_handler_data]
|
||||
@@ -10,19 +11,15 @@ impl MessageHandler<BroadcastMessage, ()> for BroadcastMessageHandler {
|
||||
fn process_message(&mut self, message: BroadcastMessage, responses: &mut VecDeque<Message>, _: ()) {
|
||||
match message {
|
||||
// Sub-messages
|
||||
BroadcastMessage::TriggerEvent(event) => {
|
||||
for message in self.listeners.entry(event).or_default() {
|
||||
responses.add_front(message.clone())
|
||||
}
|
||||
}
|
||||
BroadcastMessage::TriggerEvent(message) => self.event.process_message(message, responses, EventMessageContext { listeners: &mut self.listeners }),
|
||||
|
||||
// Messages
|
||||
BroadcastMessage::SubscribeEvent { on, send } => self.listeners.entry(on).or_default().push(*send),
|
||||
BroadcastMessage::UnsubscribeEvent { on, message } => self.listeners.entry(on).or_default().retain(|msg| *msg != *message),
|
||||
BroadcastMessage::UnsubscribeEvent { on, send } => self.listeners.entry(on).or_default().retain(|msg| *msg != *send),
|
||||
}
|
||||
}
|
||||
|
||||
fn actions(&self) -> ActionList {
|
||||
actions!(BroadcastEventDiscriminant;)
|
||||
actions!(EventMessageDiscriminant;)
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||
use crate::messages::prelude::*;
|
||||
|
||||
#[derive(PartialEq, Eq, Clone, Debug, serde::Serialize, serde::Deserialize, Hash)]
|
||||
#[impl_message(Message, BroadcastMessage, TriggerEvent)]
|
||||
pub enum BroadcastEvent {
|
||||
#[derive(PartialEq, Eq, Clone, Debug, serde::Serialize, serde::Deserialize, Hash)]
|
||||
pub enum EventMessage {
|
||||
/// Triggered by requestAnimationFrame in JS
|
||||
AnimationFrame,
|
||||
CanvasTransformed,
|
||||
@@ -0,0 +1,22 @@
|
||||
use crate::messages::prelude::*;
|
||||
|
||||
#[derive(ExtractField)]
|
||||
pub struct EventMessageContext<'a> {
|
||||
pub listeners: &'a mut HashMap<EventMessage, Vec<Message>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, ExtractField)]
|
||||
pub struct EventMessageHandler {}
|
||||
|
||||
#[message_handler_data]
|
||||
impl MessageHandler<EventMessage, EventMessageContext<'_>> for EventMessageHandler {
|
||||
fn process_message(&mut self, message: EventMessage, responses: &mut VecDeque<Message>, context: EventMessageContext) {
|
||||
for message in context.listeners.entry(message).or_default() {
|
||||
responses.add_front(message.clone())
|
||||
}
|
||||
}
|
||||
|
||||
fn actions(&self) -> ActionList {
|
||||
actions!(EventMessageDiscriminant;)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
mod event_message;
|
||||
mod event_message_handler;
|
||||
|
||||
#[doc(inline)]
|
||||
pub use event_message::{EventMessage, EventMessageDiscriminant};
|
||||
#[doc(inline)]
|
||||
pub use event_message_handler::{EventMessageContext, EventMessageHandler};
|
||||
@@ -1,7 +1,7 @@
|
||||
mod broadcast_message;
|
||||
mod broadcast_message_handler;
|
||||
|
||||
pub mod broadcast_event;
|
||||
pub mod event;
|
||||
|
||||
#[doc(inline)]
|
||||
pub use broadcast_message::{BroadcastMessage, BroadcastMessageDiscriminant};
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
use crate::messages::prelude::*;
|
||||
|
||||
#[impl_message(Message, Defer)]
|
||||
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
|
||||
pub enum DeferMessage {
|
||||
SetGraphSubmissionIndex { execution_id: u64 },
|
||||
TriggerGraphRun { execution_id: u64, document_id: DocumentId },
|
||||
AfterGraphRun { messages: Vec<Message> },
|
||||
TriggerNavigationReady,
|
||||
AfterNavigationReady { messages: Vec<Message> },
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
use crate::messages::prelude::*;
|
||||
|
||||
#[derive(ExtractField)]
|
||||
pub struct DeferMessageContext<'a> {
|
||||
pub portfolio: &'a PortfolioMessageHandler,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, ExtractField)]
|
||||
pub struct DeferMessageHandler {
|
||||
after_graph_run: HashMap<DocumentId, Vec<(u64, Message)>>,
|
||||
after_viewport_resize: Vec<Message>,
|
||||
current_graph_submission_id: u64,
|
||||
}
|
||||
|
||||
#[message_handler_data]
|
||||
impl MessageHandler<DeferMessage, DeferMessageContext<'_>> for DeferMessageHandler {
|
||||
fn process_message(&mut self, message: DeferMessage, responses: &mut VecDeque<Message>, context: DeferMessageContext) {
|
||||
match message {
|
||||
DeferMessage::AfterGraphRun { mut messages } => {
|
||||
let after_graph_run = self.after_graph_run.entry(context.portfolio.active_document_id.unwrap_or(DocumentId(0))).or_default();
|
||||
after_graph_run.extend(messages.drain(..).map(|m| (self.current_graph_submission_id, m)));
|
||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||
}
|
||||
DeferMessage::AfterNavigationReady { messages } => {
|
||||
self.after_viewport_resize.extend_from_slice(&messages);
|
||||
}
|
||||
DeferMessage::SetGraphSubmissionIndex { execution_id } => {
|
||||
self.current_graph_submission_id = execution_id + 1;
|
||||
}
|
||||
DeferMessage::TriggerGraphRun { execution_id, document_id } => {
|
||||
let after_graph_run = self.after_graph_run.entry(document_id).or_default();
|
||||
if after_graph_run.is_empty() {
|
||||
return;
|
||||
}
|
||||
// Find the index of the last message we can process
|
||||
let split = after_graph_run.partition_point(|&(id, _)| id <= execution_id);
|
||||
let elements = after_graph_run.drain(..split);
|
||||
for (_, message) in elements.rev() {
|
||||
responses.add_front(message);
|
||||
}
|
||||
for (&document_id, messages) in self.after_graph_run.iter() {
|
||||
if !messages.is_empty() {
|
||||
responses.add(PortfolioMessage::SubmitGraphRender { document_id, ignore_hash: false });
|
||||
}
|
||||
}
|
||||
}
|
||||
DeferMessage::TriggerNavigationReady => {
|
||||
for message in self.after_viewport_resize.drain(..).rev() {
|
||||
responses.add_front(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
advertise_actions!(DeferMessageDiscriminant;
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
mod defer_message;
|
||||
mod defer_message_handler;
|
||||
|
||||
#[doc(inline)]
|
||||
pub use defer_message::{DeferMessage, DeferMessageDiscriminant};
|
||||
#[doc(inline)]
|
||||
pub use defer_message_handler::{DeferMessageContext, DeferMessageHandler};
|
||||
@@ -33,6 +33,9 @@ pub enum DialogMessage {
|
||||
RequestLicensesDialogWithLocalizedCommitDate {
|
||||
localized_commit_year: String,
|
||||
},
|
||||
RequestLicensesThirdPartyDialogWithLicenseText {
|
||||
license_text: String,
|
||||
},
|
||||
RequestNewDocumentDialog,
|
||||
RequestPreferencesDialog,
|
||||
}
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
use super::new_document_dialog::NewDocumentDialogMessageContext;
|
||||
use super::simple_dialogs::{self, AboutGraphiteDialog, ComingSoonDialog, DemoArtworkDialog, LicensesDialog};
|
||||
use crate::messages::dialog::simple_dialogs::LicensesThirdPartyDialog;
|
||||
use crate::messages::input_mapper::utility_types::input_mouse::ViewportBounds;
|
||||
use crate::messages::layout::utility_types::widget_prelude::*;
|
||||
use crate::messages::prelude::*;
|
||||
|
||||
#[derive(ExtractField)]
|
||||
pub struct DialogMessageContext<'a> {
|
||||
pub portfolio: &'a PortfolioMessageHandler,
|
||||
pub viewport_bounds: &'a ViewportBounds,
|
||||
pub preferences: &'a PreferencesMessageHandler,
|
||||
}
|
||||
|
||||
@@ -19,11 +23,15 @@ pub struct DialogMessageHandler {
|
||||
#[message_handler_data]
|
||||
impl MessageHandler<DialogMessage, DialogMessageContext<'_>> for DialogMessageHandler {
|
||||
fn process_message(&mut self, message: DialogMessage, responses: &mut VecDeque<Message>, context: DialogMessageContext) {
|
||||
let DialogMessageContext { portfolio, preferences } = context;
|
||||
let DialogMessageContext {
|
||||
portfolio,
|
||||
preferences,
|
||||
viewport_bounds,
|
||||
} = context;
|
||||
|
||||
match message {
|
||||
DialogMessage::ExportDialog(message) => self.export_dialog.process_message(message, responses, ExportDialogMessageContext { portfolio }),
|
||||
DialogMessage::NewDocumentDialog(message) => self.new_document_dialog.process_message(message, responses, ()),
|
||||
DialogMessage::NewDocumentDialog(message) => self.new_document_dialog.process_message(message, responses, NewDocumentDialogMessageContext { viewport_bounds }),
|
||||
DialogMessage::PreferencesDialog(message) => self.preferences_dialog.process_message(message, responses, PreferencesDialogMessageContext { preferences }),
|
||||
|
||||
DialogMessage::CloseAllDocumentsWithConfirmation => {
|
||||
@@ -96,6 +104,10 @@ impl MessageHandler<DialogMessage, DialogMessageContext<'_>> for DialogMessageHa
|
||||
|
||||
dialog.send_dialog_to_frontend(responses);
|
||||
}
|
||||
DialogMessage::RequestLicensesThirdPartyDialogWithLicenseText { license_text } => {
|
||||
let dialog = LicensesThirdPartyDialog { license_text };
|
||||
dialog.send_dialog_to_frontend(responses);
|
||||
}
|
||||
DialogMessage::RequestNewDocumentDialog => {
|
||||
self.new_document_dialog = NewDocumentDialogMessageHandler {
|
||||
name: portfolio.generate_new_document_name(),
|
||||
|
||||
@@ -4,10 +4,10 @@ use crate::messages::prelude::*;
|
||||
#[impl_message(Message, DialogMessage, ExportDialog)]
|
||||
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
|
||||
pub enum ExportDialogMessage {
|
||||
FileType(FileType),
|
||||
ScaleFactor(f64),
|
||||
TransparentBackground(bool),
|
||||
ExportBounds(ExportBounds),
|
||||
FileType { file_type: FileType },
|
||||
ScaleFactor { factor: f64 },
|
||||
TransparentBackground { transparent: bool },
|
||||
ExportBounds { bounds: ExportBounds },
|
||||
|
||||
Submit,
|
||||
}
|
||||
|
||||
@@ -38,13 +38,13 @@ impl MessageHandler<ExportDialogMessage, ExportDialogMessageContext<'_>> for Exp
|
||||
let ExportDialogMessageContext { portfolio } = context;
|
||||
|
||||
match message {
|
||||
ExportDialogMessage::FileType(export_type) => self.file_type = export_type,
|
||||
ExportDialogMessage::ScaleFactor(factor) => self.scale_factor = factor,
|
||||
ExportDialogMessage::TransparentBackground(transparent_background) => self.transparent_background = transparent_background,
|
||||
ExportDialogMessage::ExportBounds(export_area) => self.bounds = export_area,
|
||||
ExportDialogMessage::FileType { file_type } => self.file_type = file_type,
|
||||
ExportDialogMessage::ScaleFactor { factor } => self.scale_factor = factor,
|
||||
ExportDialogMessage::TransparentBackground { transparent } => self.transparent_background = transparent,
|
||||
ExportDialogMessage::ExportBounds { bounds } => self.bounds = bounds,
|
||||
|
||||
ExportDialogMessage::Submit => responses.add_front(PortfolioMessage::SubmitDocumentExport {
|
||||
file_name: portfolio.active_document().map(|document| document.name.clone()).unwrap_or_default(),
|
||||
name: portfolio.active_document().map(|document| document.name.clone()).unwrap_or_default(),
|
||||
file_type: self.file_type,
|
||||
scale_factor: self.scale_factor,
|
||||
bounds: self.bounds,
|
||||
@@ -84,24 +84,28 @@ impl LayoutHolder for ExportDialogMessageHandler {
|
||||
fn layout(&self) -> Layout {
|
||||
let entries = [(FileType::Png, "PNG"), (FileType::Jpg, "JPG"), (FileType::Svg, "SVG")]
|
||||
.into_iter()
|
||||
.map(|(val, name)| RadioEntryData::new(format!("{val:?}")).label(name).on_update(move |_| ExportDialogMessage::FileType(val).into()))
|
||||
.map(|(file_type, name)| {
|
||||
RadioEntryData::new(format!("{file_type:?}"))
|
||||
.label(name)
|
||||
.on_update(move |_| ExportDialogMessage::FileType { file_type }.into())
|
||||
})
|
||||
.collect();
|
||||
|
||||
let export_type = vec![
|
||||
TextLabel::new("File Type").table_align(true).min_width(100).widget_holder(),
|
||||
TextLabel::new("File Type").table_align(true).min_width("100px").widget_holder(),
|
||||
Separator::new(SeparatorType::Unrelated).widget_holder(),
|
||||
RadioInput::new(entries).selected_index(Some(self.file_type as u32)).widget_holder(),
|
||||
];
|
||||
|
||||
let resolution = vec![
|
||||
TextLabel::new("Scale Factor").table_align(true).min_width(100).widget_holder(),
|
||||
TextLabel::new("Scale Factor").table_align(true).min_width("100px").widget_holder(),
|
||||
Separator::new(SeparatorType::Unrelated).widget_holder(),
|
||||
NumberInput::new(Some(self.scale_factor))
|
||||
.unit("")
|
||||
.min(0.)
|
||||
.max((1_u64 << f64::MANTISSA_DIGITS) as f64)
|
||||
.disabled(self.file_type == FileType::Svg)
|
||||
.on_update(|number_input: &NumberInput| ExportDialogMessage::ScaleFactor(number_input.value.unwrap()).into())
|
||||
.on_update(|number_input: &NumberInput| ExportDialogMessage::ScaleFactor { factor: number_input.value.unwrap() }.into())
|
||||
.min_width(200)
|
||||
.widget_holder(),
|
||||
];
|
||||
@@ -111,24 +115,24 @@ impl LayoutHolder for ExportDialogMessageHandler {
|
||||
(ExportBounds::Selection, "Selection".to_string(), !self.has_selection),
|
||||
];
|
||||
let artboards = self.artboards.iter().map(|(&layer, name)| (ExportBounds::Artboard(layer), name.to_string(), false)).collect();
|
||||
let groups = [standard_bounds, artboards];
|
||||
let choices = [standard_bounds, artboards];
|
||||
|
||||
let current_bounds = if !self.has_selection && self.bounds == ExportBounds::Selection {
|
||||
ExportBounds::AllArtwork
|
||||
} else {
|
||||
self.bounds
|
||||
};
|
||||
let index = groups.iter().flatten().position(|(bounds, _, _)| *bounds == current_bounds).unwrap();
|
||||
let index = choices.iter().flatten().position(|(bounds, _, _)| *bounds == current_bounds).unwrap();
|
||||
|
||||
let mut entries = groups
|
||||
let mut entries = choices
|
||||
.into_iter()
|
||||
.map(|group| {
|
||||
group
|
||||
.map(|choice| {
|
||||
choice
|
||||
.into_iter()
|
||||
.map(|(val, name, disabled)| {
|
||||
MenuListEntry::new(format!("{val:?}"))
|
||||
.map(|(bounds, name, disabled)| {
|
||||
MenuListEntry::new(format!("{bounds:?}"))
|
||||
.label(name)
|
||||
.on_commit(move |_| ExportDialogMessage::ExportBounds(val).into())
|
||||
.on_commit(move |_| ExportDialogMessage::ExportBounds { bounds }.into())
|
||||
.disabled(disabled)
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
@@ -140,19 +144,19 @@ impl LayoutHolder for ExportDialogMessageHandler {
|
||||
}
|
||||
|
||||
let export_area = vec![
|
||||
TextLabel::new("Bounds").table_align(true).min_width(100).widget_holder(),
|
||||
TextLabel::new("Bounds").table_align(true).min_width("100px").widget_holder(),
|
||||
Separator::new(SeparatorType::Unrelated).widget_holder(),
|
||||
DropdownInput::new(entries).selected_index(Some(index as u32)).widget_holder(),
|
||||
];
|
||||
|
||||
let mut checkbox_id = CheckboxId::default();
|
||||
let checkbox_id = CheckboxId::new();
|
||||
let transparent_background = vec![
|
||||
TextLabel::new("Transparency").table_align(true).min_width(100).for_checkbox(&mut checkbox_id).widget_holder(),
|
||||
TextLabel::new("Transparency").table_align(true).min_width("100px").for_checkbox(checkbox_id).widget_holder(),
|
||||
Separator::new(SeparatorType::Unrelated).widget_holder(),
|
||||
CheckboxInput::new(self.transparent_background)
|
||||
.disabled(self.file_type == FileType::Jpg)
|
||||
.on_update(move |value: &CheckboxInput| ExportDialogMessage::TransparentBackground(value.checked).into())
|
||||
.for_label(checkbox_id.clone())
|
||||
.on_update(move |value: &CheckboxInput| ExportDialogMessage::TransparentBackground { transparent: value.checked }.into())
|
||||
.for_label(checkbox_id)
|
||||
.widget_holder(),
|
||||
];
|
||||
|
||||
|
||||
@@ -4,4 +4,4 @@ mod new_document_dialog_message_handler;
|
||||
#[doc(inline)]
|
||||
pub use new_document_dialog_message::{NewDocumentDialogMessage, NewDocumentDialogMessageDiscriminant};
|
||||
#[doc(inline)]
|
||||
pub use new_document_dialog_message_handler::NewDocumentDialogMessageHandler;
|
||||
pub use new_document_dialog_message_handler::{NewDocumentDialogMessageContext, NewDocumentDialogMessageHandler};
|
||||
|
||||
@@ -3,10 +3,10 @@ use crate::messages::prelude::*;
|
||||
#[impl_message(Message, DialogMessage, NewDocumentDialog)]
|
||||
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
|
||||
pub enum NewDocumentDialogMessage {
|
||||
Name(String),
|
||||
Infinite(bool),
|
||||
DimensionsX(f64),
|
||||
DimensionsY(f64),
|
||||
Name { name: String },
|
||||
Infinite { infinite: bool },
|
||||
DimensionsX { width: f64 },
|
||||
DimensionsY { height: f64 },
|
||||
|
||||
Submit,
|
||||
}
|
||||
|
||||
+32
-23
@@ -1,8 +1,13 @@
|
||||
use crate::messages::layout::utility_types::widget_prelude::*;
|
||||
use crate::messages::prelude::*;
|
||||
use crate::messages::{input_mapper::utility_types::input_mouse::ViewportBounds, layout::utility_types::widget_prelude::*};
|
||||
use glam::{IVec2, UVec2};
|
||||
use graph_craft::document::NodeId;
|
||||
|
||||
#[derive(ExtractField)]
|
||||
pub struct NewDocumentDialogMessageContext<'a> {
|
||||
pub viewport_bounds: &'a ViewportBounds,
|
||||
}
|
||||
|
||||
/// A dialog to allow users to set some initial options about a new document.
|
||||
#[derive(Debug, Clone, Default, ExtractField)]
|
||||
pub struct NewDocumentDialogMessageHandler {
|
||||
@@ -12,30 +17,34 @@ pub struct NewDocumentDialogMessageHandler {
|
||||
}
|
||||
|
||||
#[message_handler_data]
|
||||
impl MessageHandler<NewDocumentDialogMessage, ()> for NewDocumentDialogMessageHandler {
|
||||
fn process_message(&mut self, message: NewDocumentDialogMessage, responses: &mut VecDeque<Message>, _: ()) {
|
||||
impl<'a> MessageHandler<NewDocumentDialogMessage, NewDocumentDialogMessageContext<'a>> for NewDocumentDialogMessageHandler {
|
||||
fn process_message(&mut self, message: NewDocumentDialogMessage, responses: &mut VecDeque<Message>, context: NewDocumentDialogMessageContext<'a>) {
|
||||
match message {
|
||||
NewDocumentDialogMessage::Name(name) => self.name = name,
|
||||
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::Name { name } => self.name = name,
|
||||
NewDocumentDialogMessage::Infinite { infinite } => self.infinite = infinite,
|
||||
NewDocumentDialogMessage::DimensionsX { width } => self.dimensions.x = width as u32,
|
||||
NewDocumentDialogMessage::DimensionsY { height } => self.dimensions.y = height as u32,
|
||||
NewDocumentDialogMessage::Submit => {
|
||||
responses.add(PortfolioMessage::NewDocumentWithName { name: self.name.clone() });
|
||||
|
||||
let create_artboard = !self.infinite && self.dimensions.x > 0 && self.dimensions.y > 0;
|
||||
if create_artboard {
|
||||
responses.add(Message::StartBuffer);
|
||||
responses.add(GraphOperationMessage::NewArtboard {
|
||||
id: NodeId::new(),
|
||||
artboard: graphene_std::Artboard::new(IVec2::ZERO, self.dimensions.as_ivec2()),
|
||||
});
|
||||
responses.add(NavigationMessage::CanvasPan { delta: self.dimensions.as_dvec2() });
|
||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||
// If we already have bounds, we won't receive a viewport bounds update so we just fabricate one ourselves
|
||||
if *context.viewport_bounds != ViewportBounds::default() {
|
||||
responses.add(InputPreprocessorMessage::BoundsOfViewports {
|
||||
bounds_of_viewports: vec![context.viewport_bounds.clone()],
|
||||
});
|
||||
}
|
||||
responses.add(DeferMessage::AfterNavigationReady {
|
||||
messages: vec![DocumentMessage::ZoomCanvasToFitAll.into(), DocumentMessage::DeselectAllLayers.into()],
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: Figure out how to get StartBuffer to work here so we can delete this and use `DocumentMessage::ZoomCanvasToFitAll` instead
|
||||
// Currently, it is necessary to use `FrontendMessage::TriggerDelayedZoomCanvasToFitAll` rather than `DocumentMessage::ZoomCanvasToFitAll` because the size of the viewport is not yet populated
|
||||
responses.add(Message::StartBuffer);
|
||||
responses.add(FrontendMessage::TriggerDelayedZoomCanvasToFitAll);
|
||||
responses.add(DocumentMessage::DeselectAllLayers);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,26 +79,26 @@ impl DialogLayoutHolder for NewDocumentDialogMessageHandler {
|
||||
impl LayoutHolder for NewDocumentDialogMessageHandler {
|
||||
fn layout(&self) -> Layout {
|
||||
let name = vec![
|
||||
TextLabel::new("Name").table_align(true).min_width(90).widget_holder(),
|
||||
TextLabel::new("Name").table_align(true).min_width("90px").widget_holder(),
|
||||
Separator::new(SeparatorType::Unrelated).widget_holder(),
|
||||
TextInput::new(&self.name)
|
||||
.on_update(|text_input: &TextInput| NewDocumentDialogMessage::Name(text_input.value.clone()).into())
|
||||
.on_update(|text_input: &TextInput| NewDocumentDialogMessage::Name { name: text_input.value.clone() }.into())
|
||||
.min_width(204) // Matches the 100px of both NumberInputs below + the 4px of the Unrelated-type separator
|
||||
.widget_holder(),
|
||||
];
|
||||
|
||||
let mut checkbox_id = CheckboxId::default();
|
||||
let checkbox_id = CheckboxId::new();
|
||||
let infinite = vec![
|
||||
TextLabel::new("Infinite Canvas").table_align(true).min_width(90).for_checkbox(&mut checkbox_id).widget_holder(),
|
||||
TextLabel::new("Infinite Canvas").table_align(true).min_width("90px").for_checkbox(checkbox_id).widget_holder(),
|
||||
Separator::new(SeparatorType::Unrelated).widget_holder(),
|
||||
CheckboxInput::new(self.infinite)
|
||||
.on_update(|checkbox_input: &CheckboxInput| NewDocumentDialogMessage::Infinite(checkbox_input.checked).into())
|
||||
.for_label(checkbox_id.clone())
|
||||
.on_update(|checkbox_input: &CheckboxInput| NewDocumentDialogMessage::Infinite { infinite: checkbox_input.checked }.into())
|
||||
.for_label(checkbox_id)
|
||||
.widget_holder(),
|
||||
];
|
||||
|
||||
let scale = vec![
|
||||
TextLabel::new("Dimensions").table_align(true).min_width(90).widget_holder(),
|
||||
TextLabel::new("Dimensions").table_align(true).min_width("90px").widget_holder(),
|
||||
Separator::new(SeparatorType::Unrelated).widget_holder(),
|
||||
NumberInput::new(Some(self.dimensions.x as f64))
|
||||
.label("W")
|
||||
@@ -99,7 +108,7 @@ impl LayoutHolder for NewDocumentDialogMessageHandler {
|
||||
.is_integer(true)
|
||||
.disabled(self.infinite)
|
||||
.min_width(100)
|
||||
.on_update(|number_input: &NumberInput| NewDocumentDialogMessage::DimensionsX(number_input.value.unwrap()).into())
|
||||
.on_update(|number_input: &NumberInput| NewDocumentDialogMessage::DimensionsX { width: number_input.value.unwrap() }.into())
|
||||
.widget_holder(),
|
||||
Separator::new(SeparatorType::Related).widget_holder(),
|
||||
NumberInput::new(Some(self.dimensions.y as f64))
|
||||
@@ -110,7 +119,7 @@ impl LayoutHolder for NewDocumentDialogMessageHandler {
|
||||
.is_integer(true)
|
||||
.disabled(self.infinite)
|
||||
.min_width(100)
|
||||
.on_update(|number_input: &NumberInput| NewDocumentDialogMessage::DimensionsY(number_input.value.unwrap()).into())
|
||||
.on_update(|number_input: &NumberInput| NewDocumentDialogMessage::DimensionsY { height: number_input.value.unwrap() }.into())
|
||||
.widget_holder(),
|
||||
];
|
||||
|
||||
|
||||
+9
-13
@@ -68,7 +68,7 @@ impl PreferencesDialogMessageHandler {
|
||||
.widget_holder(),
|
||||
];
|
||||
|
||||
let mut checkbox_id = CheckboxId::default();
|
||||
let checkbox_id = CheckboxId::new();
|
||||
let zoom_with_scroll_tooltip = "Use the scroll wheel for zooming instead of vertically panning (not recommended for trackpads)";
|
||||
let zoom_with_scroll = vec![
|
||||
Separator::new(SeparatorType::Unrelated).widget_holder(),
|
||||
@@ -81,12 +81,12 @@ impl PreferencesDialogMessageHandler {
|
||||
}
|
||||
.into()
|
||||
})
|
||||
.for_label(checkbox_id.clone())
|
||||
.for_label(checkbox_id)
|
||||
.widget_holder(),
|
||||
TextLabel::new("Zoom with Scroll")
|
||||
.table_align(true)
|
||||
.tooltip(zoom_with_scroll_tooltip)
|
||||
.for_checkbox(&mut checkbox_id)
|
||||
.for_checkbox(checkbox_id)
|
||||
.widget_holder(),
|
||||
];
|
||||
|
||||
@@ -169,7 +169,7 @@ impl PreferencesDialogMessageHandler {
|
||||
graph_wire_style,
|
||||
];
|
||||
|
||||
let mut checkbox_id = CheckboxId::default();
|
||||
let checkbox_id = CheckboxId::new();
|
||||
let vello_tooltip = "Use the experimental Vello renderer (your browser must support WebGPU)";
|
||||
let use_vello = vec![
|
||||
Separator::new(SeparatorType::Unrelated).widget_holder(),
|
||||
@@ -178,17 +178,17 @@ impl PreferencesDialogMessageHandler {
|
||||
.tooltip(vello_tooltip)
|
||||
.disabled(!preferences.supports_wgpu())
|
||||
.on_update(|checkbox_input: &CheckboxInput| PreferencesMessage::UseVello { use_vello: checkbox_input.checked }.into())
|
||||
.for_label(checkbox_id.clone())
|
||||
.for_label(checkbox_id)
|
||||
.widget_holder(),
|
||||
TextLabel::new("Vello Renderer")
|
||||
.table_align(true)
|
||||
.tooltip(vello_tooltip)
|
||||
.disabled(!preferences.supports_wgpu())
|
||||
.for_checkbox(&mut checkbox_id)
|
||||
.for_checkbox(checkbox_id)
|
||||
.widget_holder(),
|
||||
];
|
||||
|
||||
let mut checkbox_id = CheckboxId::default();
|
||||
let checkbox_id = CheckboxId::new();
|
||||
let vector_mesh_tooltip =
|
||||
"Allow tools to produce vector meshes, where more than two segments can connect to an anchor point.\n\nCurrently this does not properly handle stroke joins and fills.";
|
||||
let vector_meshes = vec![
|
||||
@@ -197,13 +197,9 @@ impl PreferencesDialogMessageHandler {
|
||||
CheckboxInput::new(preferences.vector_meshes)
|
||||
.tooltip(vector_mesh_tooltip)
|
||||
.on_update(|checkbox_input: &CheckboxInput| PreferencesMessage::VectorMeshes { enabled: checkbox_input.checked }.into())
|
||||
.for_label(checkbox_id.clone())
|
||||
.widget_holder(),
|
||||
TextLabel::new("Vector Meshes")
|
||||
.table_align(true)
|
||||
.tooltip(vector_mesh_tooltip)
|
||||
.for_checkbox(&mut checkbox_id)
|
||||
.for_label(checkbox_id)
|
||||
.widget_holder(),
|
||||
TextLabel::new("Vector Meshes").table_align(true).tooltip(vector_mesh_tooltip).for_checkbox(checkbox_id).widget_holder(),
|
||||
];
|
||||
|
||||
Layout::WidgetLayout(WidgetLayout::new(vec![
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::messages::broadcast::broadcast_event::BroadcastEvent;
|
||||
use crate::messages::broadcast::event::EventMessage;
|
||||
use crate::messages::layout::utility_types::widget_prelude::*;
|
||||
use crate::messages::prelude::*;
|
||||
|
||||
@@ -27,7 +27,7 @@ impl DialogLayoutHolder for CloseDocumentDialog {
|
||||
TextButton::new("Discard")
|
||||
.on_update(move |_| {
|
||||
DialogMessage::CloseDialogAndThen {
|
||||
followups: vec![BroadcastEvent::ToolAbort.into(), PortfolioMessage::CloseDocument { document_id }.into()],
|
||||
followups: vec![EventMessage::ToolAbort.into(), PortfolioMessage::CloseDocument { document_id }.into()],
|
||||
}
|
||||
.into()
|
||||
})
|
||||
|
||||
@@ -16,22 +16,31 @@ impl DialogLayoutHolder for LicensesDialog {
|
||||
}
|
||||
|
||||
fn layout_column_2(&self) -> Layout {
|
||||
let icons_license_link = "https://raw.githubusercontent.com/GraphiteEditor/Graphite/master/frontend/assets/LICENSE.md";
|
||||
let links = [
|
||||
("GraphiteLogo", "Graphite Logo", "https://graphite.rs/logo/"),
|
||||
("IconsGrid", "Graphite Icons", icons_license_link),
|
||||
("License", "Graphite License", "https://graphite.rs/license/"),
|
||||
("License", "Other Licenses", "/third-party-licenses.txt"),
|
||||
#[allow(clippy::type_complexity)]
|
||||
let button_definitions: &[(&str, &str, fn() -> Message)] = &[
|
||||
("GraphiteLogo", "Graphite Logo", || {
|
||||
FrontendMessage::TriggerVisitLink {
|
||||
url: "https://graphite.rs/logo/".into(),
|
||||
}
|
||||
.into()
|
||||
}),
|
||||
("IconsGrid", "Graphite Icons", || {
|
||||
FrontendMessage::TriggerVisitLink {
|
||||
url: "https://raw.githubusercontent.com/GraphiteEditor/Graphite/master/frontend/assets/LICENSE.md".into(),
|
||||
}
|
||||
.into()
|
||||
}),
|
||||
("License", "Graphite License", || {
|
||||
FrontendMessage::TriggerVisitLink {
|
||||
url: "https://graphite.rs/license/".into(),
|
||||
}
|
||||
.into()
|
||||
}),
|
||||
("License", "Other Licenses", || FrontendMessage::TriggerDisplayThirdPartyLicensesDialog.into()),
|
||||
];
|
||||
let widgets = links
|
||||
.into_iter()
|
||||
.map(|(icon, label, url)| {
|
||||
TextButton::new(label)
|
||||
.icon(Some(icon.into()))
|
||||
.flush(true)
|
||||
.on_update(|_| FrontendMessage::TriggerVisitLink { url: url.into() }.into())
|
||||
.widget_holder()
|
||||
})
|
||||
let widgets = button_definitions
|
||||
.iter()
|
||||
.map(|&(icon, label, message_factory)| TextButton::new(label).icon(Some((icon).into())).flush(true).on_update(move |_| message_factory()).widget_holder())
|
||||
.collect();
|
||||
|
||||
Layout::WidgetLayout(WidgetLayout::new(vec![LayoutGroup::Column { widgets }]))
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
use crate::messages::layout::utility_types::widget_prelude::*;
|
||||
use crate::messages::prelude::*;
|
||||
|
||||
pub struct LicensesThirdPartyDialog {
|
||||
pub license_text: String,
|
||||
}
|
||||
|
||||
impl DialogLayoutHolder for LicensesThirdPartyDialog {
|
||||
const ICON: &'static str = "License12px";
|
||||
const TITLE: &'static str = "Third-Party Software License Notices";
|
||||
|
||||
fn layout_buttons(&self) -> Layout {
|
||||
let widgets = vec![TextButton::new("OK").emphasized(true).on_update(|_| FrontendMessage::DisplayDialogDismiss.into()).widget_holder()];
|
||||
|
||||
Layout::WidgetLayout(WidgetLayout::new(vec![LayoutGroup::Row { widgets }]))
|
||||
}
|
||||
}
|
||||
|
||||
impl LayoutHolder for LicensesThirdPartyDialog {
|
||||
fn layout(&self) -> Layout {
|
||||
// Remove the header and begin with the line containing the first license section (we otherwise keep the title for standalone viewing of the licenses text file)
|
||||
let license_text = if let Some(first_underscore_line) = self.license_text.lines().position(|line| line.contains('_')) {
|
||||
// Find the byte position where the line with underscore starts
|
||||
let char_position = self.license_text.split('\n').take(first_underscore_line).map(|line| line.len() + '\n'.len_utf8()).sum();
|
||||
self.license_text[char_position..].to_string()
|
||||
} else {
|
||||
// This shouldn't be encountered, but if no underscore line is found, we use the full text as a safety fallback
|
||||
self.license_text.clone()
|
||||
};
|
||||
|
||||
// Two characters (one before, one after) the sequence of underscore characters, plus one additional column to provide a space between the text and the scrollbar
|
||||
let non_wrapping_column_width = license_text.split('\n').map(|line| line.chars().filter(|&c| c == '_').count()).max().unwrap_or(0) + 2 + 1;
|
||||
|
||||
Layout::WidgetLayout(WidgetLayout::new(vec![LayoutGroup::Row {
|
||||
widgets: vec![
|
||||
TextLabel::new(license_text)
|
||||
.monospace(true)
|
||||
.multiline(true)
|
||||
.min_width(format!("{non_wrapping_column_width}ch"))
|
||||
.widget_holder(),
|
||||
],
|
||||
}]))
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ mod coming_soon_dialog;
|
||||
mod demo_artwork_dialog;
|
||||
mod error_dialog;
|
||||
mod licenses_dialog;
|
||||
mod licenses_third_party_dialog;
|
||||
|
||||
pub use about_graphite_dialog::AboutGraphiteDialog;
|
||||
pub use close_all_documents_dialog::CloseAllDocumentsDialog;
|
||||
@@ -14,3 +15,4 @@ pub use demo_artwork_dialog::ARTWORK;
|
||||
pub use demo_artwork_dialog::DemoArtworkDialog;
|
||||
pub use error_dialog::ErrorDialog;
|
||||
pub use licenses_dialog::LicensesDialog;
|
||||
pub use licenses_third_party_dialog::LicensesThirdPartyDialog;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use super::utility_types::{FrontendDocumentDetails, MouseCursorIcon};
|
||||
use crate::messages::app_window::app_window_message_handler::AppWindowPlatform;
|
||||
use crate::messages::layout::utility_types::widget_prelude::*;
|
||||
use crate::messages::portfolio::document::node_graph::utility_types::{
|
||||
BoxSelection, ContextMenuInformation, FrontendClickTargets, FrontendGraphInput, FrontendGraphOutput, FrontendNode, FrontendNodeType, Transform,
|
||||
@@ -7,12 +8,19 @@ use crate::messages::portfolio::document::utility_types::nodes::{JsRawBuffer, La
|
||||
use crate::messages::portfolio::document::utility_types::wires::{WirePath, WirePathUpdate};
|
||||
use crate::messages::prelude::*;
|
||||
use crate::messages::tool::utility_types::HintData;
|
||||
use glam::IVec2;
|
||||
use graph_craft::document::NodeId;
|
||||
use graphene_std::raster::Image;
|
||||
use graphene_std::raster::color::Color;
|
||||
use graphene_std::text::Font;
|
||||
use graphene_std::text::{Font, TextAlign};
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
use crate::messages::portfolio::document::overlays::utility_types::OverlayContext;
|
||||
|
||||
#[impl_message(Message, Frontend)]
|
||||
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)]
|
||||
#[derive(derivative::Derivative, Clone, serde::Serialize, serde::Deserialize, specta::Type)]
|
||||
#[derivative(Debug, PartialEq)]
|
||||
pub enum FrontendMessage {
|
||||
// Display prefix: make the frontend show something, like a dialog
|
||||
DisplayDialog {
|
||||
@@ -37,6 +45,7 @@ pub enum FrontendMessage {
|
||||
max_width: Option<f64>,
|
||||
#[serde(rename = "maxHeight")]
|
||||
max_height: Option<f64>,
|
||||
align: TextAlign,
|
||||
},
|
||||
DisplayEditableTextboxTransform {
|
||||
transform: [f64; 6],
|
||||
@@ -56,17 +65,23 @@ pub enum FrontendMessage {
|
||||
#[serde(rename = "commitDate")]
|
||||
commit_date: String,
|
||||
},
|
||||
TriggerDelayedZoomCanvasToFitAll,
|
||||
TriggerDownloadImage {
|
||||
TriggerDisplayThirdPartyLicensesDialog,
|
||||
TriggerSaveDocument {
|
||||
document_id: DocumentId,
|
||||
name: String,
|
||||
path: Option<PathBuf>,
|
||||
content: Vec<u8>,
|
||||
},
|
||||
TriggerSaveFile {
|
||||
name: String,
|
||||
content: Vec<u8>,
|
||||
},
|
||||
TriggerExportImage {
|
||||
svg: String,
|
||||
name: String,
|
||||
mime: String,
|
||||
size: (f64, f64),
|
||||
},
|
||||
TriggerDownloadTextFile {
|
||||
document: String,
|
||||
name: String,
|
||||
},
|
||||
TriggerFetchAndOpenDocument {
|
||||
name: String,
|
||||
filename: String,
|
||||
@@ -110,12 +125,19 @@ pub enum FrontendMessage {
|
||||
document_id: DocumentId,
|
||||
},
|
||||
UpdateImportsExports {
|
||||
imports: Vec<(FrontendGraphOutput, i32, i32)>,
|
||||
exports: Vec<(FrontendGraphInput, i32, i32)>,
|
||||
#[serde(rename = "addImport")]
|
||||
add_import: Option<(i32, i32)>,
|
||||
#[serde(rename = "addExport")]
|
||||
add_export: Option<(i32, i32)>,
|
||||
/// If the primary import is not visible, then it is None.
|
||||
imports: Vec<Option<FrontendGraphOutput>>,
|
||||
/// If the primary export is not visible, then it is None.
|
||||
exports: Vec<Option<FrontendGraphInput>>,
|
||||
/// The primary import location.
|
||||
#[serde(rename = "importPosition")]
|
||||
import_position: IVec2,
|
||||
/// The primary export location.
|
||||
#[serde(rename = "exportPosition")]
|
||||
export_position: IVec2,
|
||||
/// The document network does not have an add import or export button.
|
||||
#[serde(rename = "addImportExport")]
|
||||
add_import_export: bool,
|
||||
},
|
||||
UpdateInSelectedNetwork {
|
||||
#[serde(rename = "inSelectedNetwork")]
|
||||
@@ -136,11 +158,16 @@ pub enum FrontendMessage {
|
||||
UpdateGraphViewOverlay {
|
||||
open: bool,
|
||||
},
|
||||
UpdateSpreadsheetState {
|
||||
UpdateDataPanelState {
|
||||
open: bool,
|
||||
node: Option<NodeId>,
|
||||
},
|
||||
UpdateSpreadsheetLayout {
|
||||
UpdatePropertiesPanelState {
|
||||
open: bool,
|
||||
},
|
||||
UpdateLayersPanelState {
|
||||
open: bool,
|
||||
},
|
||||
UpdateDataPanelLayout {
|
||||
#[serde(rename = "layoutTarget")]
|
||||
layout_target: LayoutTarget,
|
||||
diff: Vec<WidgetDiff>,
|
||||
@@ -179,6 +206,9 @@ pub enum FrontendMessage {
|
||||
UpdateDocumentArtwork {
|
||||
svg: String,
|
||||
},
|
||||
UpdateImageData {
|
||||
image_data: Vec<(u64, Image<Color>)>,
|
||||
},
|
||||
UpdateDocumentBarLayout {
|
||||
#[serde(rename = "layoutTarget")]
|
||||
layout_target: LayoutTarget,
|
||||
@@ -280,7 +310,7 @@ pub enum FrontendMessage {
|
||||
#[serde(rename = "openDocuments")]
|
||||
open_documents: Vec<FrontendDocumentDetails>,
|
||||
},
|
||||
UpdatePropertyPanelSectionsLayout {
|
||||
UpdatePropertiesPanelLayout {
|
||||
#[serde(rename = "layoutTarget")]
|
||||
layout_target: LayoutTarget,
|
||||
diff: Vec<WidgetDiff>,
|
||||
@@ -304,4 +334,21 @@ pub enum FrontendMessage {
|
||||
layout_target: LayoutTarget,
|
||||
diff: Vec<WidgetDiff>,
|
||||
},
|
||||
UpdatePlatform {
|
||||
platform: AppWindowPlatform,
|
||||
},
|
||||
UpdateWindowState {
|
||||
maximized: bool,
|
||||
minimized: bool,
|
||||
},
|
||||
CloseWindow,
|
||||
UpdateViewportHolePunch {
|
||||
active: bool,
|
||||
},
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
RenderOverlays {
|
||||
#[serde(skip, default = "OverlayContext::default")]
|
||||
#[derivative(Debug = "ignore", PartialEq = "ignore")]
|
||||
context: OverlayContext,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -96,7 +96,7 @@ pub fn input_mappings() -> Mapping {
|
||||
entry!(PointerMove; refresh_keys=[Control, Shift], action_dispatch=TransformLayerMessage::PointerMove { slow_key: Shift, increments_key: Control }),
|
||||
//
|
||||
// SelectToolMessage
|
||||
entry!(PointerMove; refresh_keys=[Control, Alt, Shift], action_dispatch=SelectToolMessage::PointerMove(SelectToolPointerKeys { axis_align: Shift, snap_angle: Shift, center: Alt, duplicate: Alt })),
|
||||
entry!(PointerMove; refresh_keys=[Control, Alt, Shift], action_dispatch=SelectToolMessage::PointerMove { modifier_keys: SelectToolPointerKeys { axis_align: Shift, snap_angle: Shift, center: Alt, duplicate: Alt } }),
|
||||
entry!(KeyDown(MouseLeft); action_dispatch=SelectToolMessage::DragStart { extend_selection: Shift, remove_from_selection: Alt, select_deepest: Accel, lasso_select: Control, skew: Control }),
|
||||
entry!(KeyUp(MouseLeft); action_dispatch=SelectToolMessage::DragStop { remove_from_selection: Alt }),
|
||||
entry!(KeyDown(Enter); action_dispatch=SelectToolMessage::Enter),
|
||||
@@ -178,7 +178,7 @@ pub fn input_mappings() -> Mapping {
|
||||
entry!(KeyDown(Escape); action_dispatch=ShapeToolMessage::Abort),
|
||||
entry!(KeyDown(BracketLeft); action_dispatch=ShapeToolMessage::DecreaseSides),
|
||||
entry!(KeyDown(BracketRight); action_dispatch=ShapeToolMessage::IncreaseSides),
|
||||
entry!(PointerMove; refresh_keys=[Alt, Shift, Control], action_dispatch=ShapeToolMessage::PointerMove([Alt, Shift, Control, Shift])),
|
||||
entry!(PointerMove; refresh_keys=[Alt, Shift, Control], action_dispatch=ShapeToolMessage::PointerMove { modifier: [Alt, Shift, Control] }),
|
||||
entry!(KeyDown(ArrowUp); modifiers=[Shift, ArrowLeft], action_dispatch=ShapeToolMessage::NudgeSelectedLayers { delta_x: -BIG_NUDGE_AMOUNT, delta_y: -BIG_NUDGE_AMOUNT, resize: Alt, resize_opposite_corner: Control }),
|
||||
entry!(KeyDown(ArrowUp); modifiers=[Shift, ArrowRight], action_dispatch=ShapeToolMessage::NudgeSelectedLayers { delta_x: BIG_NUDGE_AMOUNT, delta_y: -BIG_NUDGE_AMOUNT, resize: Alt, resize_opposite_corner: Control }),
|
||||
entry!(KeyDown(ArrowUp); modifiers=[Shift], action_dispatch=ShapeToolMessage::NudgeSelectedLayers { delta_x: 0., delta_y: -BIG_NUDGE_AMOUNT, resize: Alt, resize_opposite_corner: Control }),
|
||||
@@ -211,18 +211,21 @@ pub fn input_mappings() -> Mapping {
|
||||
entry!(KeyDown(Backspace); modifiers=[Accel], action_dispatch=PathToolMessage::DeleteAndBreakPath),
|
||||
entry!(KeyDown(Delete); modifiers=[Shift], action_dispatch=PathToolMessage::BreakPath),
|
||||
entry!(KeyDown(Backspace); modifiers=[Shift], action_dispatch=PathToolMessage::BreakPath),
|
||||
entry!(KeyDown(KeyX); modifiers=[Accel], action_dispatch=PathToolMessage::Cut { clipboard: Clipboard::Device }),
|
||||
entry!(KeyDown(KeyC); modifiers=[Accel], action_dispatch=PathToolMessage::Copy { clipboard: Clipboard::Device }),
|
||||
entry!(KeyDown(KeyD); modifiers=[Accel], action_dispatch=PathToolMessage::Duplicate),
|
||||
entry!(KeyDownNoRepeat(Tab); action_dispatch=PathToolMessage::SwapSelectedHandles),
|
||||
entry!(KeyDown(MouseLeft); action_dispatch=PathToolMessage::MouseDown { extend_selection: Shift, lasso_select: Control, handle_drag_from_anchor: Alt, drag_restore_handle: Control, molding_in_segment_edit: KeyA }),
|
||||
entry!(KeyDown(MouseLeft); action_dispatch=PathToolMessage::MouseDown { extend_selection: Shift, lasso_select: Control, handle_drag_from_anchor: Alt, drag_restore_handle: Control, segment_editing_modifier: Control }),
|
||||
entry!(KeyDown(MouseRight); action_dispatch=PathToolMessage::RightClick),
|
||||
entry!(KeyDown(Escape); action_dispatch=PathToolMessage::Escape),
|
||||
entry!(KeyDown(KeyG); action_dispatch=PathToolMessage::GRS { key: KeyG }),
|
||||
entry!(KeyDown(KeyR); action_dispatch=PathToolMessage::GRS { key: KeyR }),
|
||||
entry!(KeyDown(KeyS); action_dispatch=PathToolMessage::GRS { key: KeyS }),
|
||||
entry!(PointerMove; refresh_keys=[KeyC, Space, Control, Shift, Alt], action_dispatch=PathToolMessage::PointerMove { toggle_colinear: KeyC, equidistant: Alt, move_anchor_with_handles: Space, snap_angle: Shift, lock_angle: Control, delete_segment: Alt, break_colinear_molding: Alt }),
|
||||
entry!(PointerMove; refresh_keys=[KeyC, Space, Control, Shift, Alt], action_dispatch=PathToolMessage::PointerMove { toggle_colinear: KeyC, equidistant: Alt, move_anchor_with_handles: Space, snap_angle: Shift, lock_angle: Control, delete_segment: Alt, break_colinear_molding: Alt, segment_editing_modifier: Control }),
|
||||
entry!(KeyDown(Delete); action_dispatch=PathToolMessage::Delete),
|
||||
entry!(KeyDown(KeyA); modifiers=[Accel], action_dispatch=PathToolMessage::SelectAllAnchors),
|
||||
entry!(KeyDown(KeyA); modifiers=[Accel, Shift], canonical, action_dispatch=PathToolMessage::DeselectAllPoints),
|
||||
entry!(KeyDown(KeyA); modifiers=[Alt], action_dispatch=PathToolMessage::DeselectAllPoints),
|
||||
entry!(KeyDown(KeyA); modifiers=[Accel], action_dispatch=PathToolMessage::SelectAll),
|
||||
entry!(KeyDown(KeyA); modifiers=[Accel, Shift], canonical, action_dispatch=PathToolMessage::DeselectAllSelected),
|
||||
entry!(KeyDown(KeyA); modifiers=[Alt], action_dispatch=PathToolMessage::DeselectAllSelected),
|
||||
entry!(KeyDown(Backspace); action_dispatch=PathToolMessage::Delete),
|
||||
entry!(KeyUp(MouseLeft); action_dispatch=PathToolMessage::DragStop { extend_selection: Shift, shrink_selection: Alt }),
|
||||
entry!(KeyDown(Enter); action_dispatch=PathToolMessage::Enter { extend_selection: Shift, shrink_selection: Alt }),
|
||||
@@ -294,8 +297,8 @@ pub fn input_mappings() -> Mapping {
|
||||
entry!(PointerMove; action_dispatch=BrushToolMessage::PointerMove),
|
||||
entry!(KeyDown(MouseLeft); action_dispatch=BrushToolMessage::DragStart),
|
||||
entry!(KeyUp(MouseLeft); action_dispatch=BrushToolMessage::DragStop),
|
||||
entry!(KeyDown(BracketLeft); action_dispatch=BrushToolMessage::UpdateOptions(BrushToolMessageOptionsUpdate::ChangeDiameter(-BRUSH_SIZE_CHANGE_KEYBOARD))),
|
||||
entry!(KeyDown(BracketRight); action_dispatch=BrushToolMessage::UpdateOptions(BrushToolMessageOptionsUpdate::ChangeDiameter(BRUSH_SIZE_CHANGE_KEYBOARD))),
|
||||
entry!(KeyDown(BracketLeft); action_dispatch=BrushToolMessage::UpdateOptions { options: BrushToolMessageOptionsUpdate::ChangeDiameter(-BRUSH_SIZE_CHANGE_KEYBOARD) }),
|
||||
entry!(KeyDown(BracketRight); action_dispatch=BrushToolMessage::UpdateOptions { options: BrushToolMessageOptionsUpdate::ChangeDiameter(BRUSH_SIZE_CHANGE_KEYBOARD) }),
|
||||
entry!(KeyDown(MouseRight); action_dispatch=BrushToolMessage::Abort),
|
||||
entry!(KeyDown(Escape); action_dispatch=BrushToolMessage::Abort),
|
||||
//
|
||||
@@ -337,6 +340,7 @@ pub fn input_mappings() -> Mapping {
|
||||
entry!(KeyDown(KeyA); modifiers=[Accel, Shift], canonical, action_dispatch=DocumentMessage::DeselectAllLayers),
|
||||
entry!(KeyDown(KeyA); modifiers=[Alt], action_dispatch=DocumentMessage::DeselectAllLayers),
|
||||
entry!(KeyDown(KeyS); modifiers=[Accel], action_dispatch=DocumentMessage::SaveDocument),
|
||||
entry!(KeyDown(KeyS); modifiers=[Accel, Shift], action_dispatch=DocumentMessage::SaveDocumentAs),
|
||||
entry!(KeyDown(KeyD); modifiers=[Accel], canonical, action_dispatch=DocumentMessage::DuplicateSelectedLayers),
|
||||
entry!(KeyDown(KeyJ); modifiers=[Accel], action_dispatch=DocumentMessage::DuplicateSelectedLayers),
|
||||
entry!(KeyDown(KeyG); modifiers=[Accel], action_dispatch=DocumentMessage::GroupSelectedLayers { group_folder_type: GroupFolderType::Layer }),
|
||||
@@ -424,6 +428,7 @@ pub fn input_mappings() -> Mapping {
|
||||
entry!(KeyDown(KeyX); modifiers=[Accel], action_dispatch=PortfolioMessage::Cut { clipboard: Clipboard::Device }),
|
||||
entry!(KeyDown(KeyC); modifiers=[Accel], action_dispatch=PortfolioMessage::Copy { clipboard: Clipboard::Device }),
|
||||
entry!(KeyDown(KeyR); modifiers=[Alt], action_dispatch=PortfolioMessage::ToggleRulers),
|
||||
entry!(KeyDown(KeyD); modifiers=[Alt], action_dispatch=PortfolioMessage::ToggleDataPanelOpen),
|
||||
//
|
||||
// FrontendMessage
|
||||
entry!(KeyDown(KeyV); modifiers=[Accel], action_dispatch=FrontendMessage::TriggerPaste),
|
||||
|
||||
@@ -3,13 +3,16 @@ use crate::messages::prelude::*;
|
||||
#[impl_message(Message, KeyMapping)]
|
||||
#[derive(PartialEq, Eq, Clone, Debug, Hash, serde::Serialize, serde::Deserialize)]
|
||||
pub enum KeyMappingMessage {
|
||||
// Sub-messages
|
||||
#[child]
|
||||
Lookup(InputMapperMessage),
|
||||
#[child]
|
||||
ModifyMapping(MappingVariant),
|
||||
|
||||
// Messages
|
||||
ModifyMapping {
|
||||
mapping: MappingVariant,
|
||||
},
|
||||
}
|
||||
|
||||
#[impl_message(Message, KeyMappingMessage, ModifyMapping)]
|
||||
#[derive(PartialEq, Eq, Clone, Debug, Default, Hash, serde::Serialize, serde::Deserialize)]
|
||||
pub enum MappingVariant {
|
||||
#[default]
|
||||
|
||||
@@ -19,8 +19,11 @@ impl MessageHandler<KeyMappingMessage, KeyMappingMessageContext<'_>> for KeyMapp
|
||||
let KeyMappingMessageContext { input, actions } = context;
|
||||
|
||||
match message {
|
||||
// Sub-messages
|
||||
KeyMappingMessage::Lookup(input_message) => self.mapping_handler.process_message(input_message, responses, InputMapperMessageContext { input, actions }),
|
||||
KeyMappingMessage::ModifyMapping(new_layout) => self.mapping_handler.set_mapping(new_layout.into()),
|
||||
|
||||
// Messages
|
||||
KeyMappingMessage::ModifyMapping { mapping } => self.mapping_handler.set_mapping(mapping.into()),
|
||||
}
|
||||
}
|
||||
advertise_actions!();
|
||||
|
||||
@@ -2,6 +2,6 @@ mod key_mapping_message;
|
||||
mod key_mapping_message_handler;
|
||||
|
||||
#[doc(inline)]
|
||||
pub use key_mapping_message::{KeyMappingMessage, KeyMappingMessageDiscriminant, MappingVariant, MappingVariantDiscriminant};
|
||||
pub use key_mapping_message::{KeyMappingMessage, KeyMappingMessageDiscriminant, MappingVariant};
|
||||
#[doc(inline)]
|
||||
pub use key_mapping_message_handler::{KeyMappingMessageContext, KeyMappingMessageHandler};
|
||||
|
||||
@@ -479,7 +479,7 @@ impl<const LENGTH: usize> Iterator for BitVectorIter<'_, LENGTH> {
|
||||
impl<const LENGTH: usize> Display for BitVector<LENGTH> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
for storage in self.0.iter().rev() {
|
||||
write!(f, "{:0width$b}", storage, width = STORAGE_SIZE_BITS)?;
|
||||
write!(f, "{storage:0STORAGE_SIZE_BITS$b}")?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -36,6 +36,14 @@ impl MessageHandler<InputPreprocessorMessage, InputPreprocessorMessageContext> f
|
||||
responses.add(NavigationMessage::CanvasPan { delta: DVec2::ZERO });
|
||||
responses.add(NodeGraphMessage::SetGridAlignedEdges);
|
||||
}
|
||||
responses.add(DeferMessage::AfterGraphRun {
|
||||
messages: vec![
|
||||
DeferMessage::AfterGraphRun {
|
||||
messages: vec![DeferMessage::TriggerNavigationReady.into()],
|
||||
}
|
||||
.into(),
|
||||
],
|
||||
});
|
||||
}
|
||||
InputPreprocessorMessage::DoubleClick { editor_mouse_state, modifier_keys } => {
|
||||
self.update_states_of_modifier_keys(modifier_keys, keyboard_platform, responses);
|
||||
|
||||
@@ -59,8 +59,8 @@ impl LayoutMessageHandler {
|
||||
/// Get the widget path for the widget with the specified id
|
||||
fn get_widget_path(widget_layout: &WidgetLayout, widget_id: WidgetId) -> Option<(&WidgetHolder, Vec<usize>)> {
|
||||
let mut stack = widget_layout.layout.iter().enumerate().map(|(index, val)| (vec![index], val)).collect::<Vec<_>>();
|
||||
while let Some((mut widget_path, group)) = stack.pop() {
|
||||
match group {
|
||||
while let Some((mut widget_path, layout_group)) = stack.pop() {
|
||||
match layout_group {
|
||||
// Check if any of the widgets in the current column or row have the correct id
|
||||
LayoutGroup::Column { widgets } | LayoutGroup::Row { widgets } => {
|
||||
for (index, widget) in widgets.iter().enumerate() {
|
||||
@@ -308,6 +308,7 @@ impl LayoutMessageHandler {
|
||||
|
||||
responses.add(callback_message);
|
||||
}
|
||||
Widget::ImageLabel(_) => {}
|
||||
Widget::IconLabel(_) => {}
|
||||
Widget::InvisibleStandinInput(invisible) => {
|
||||
let callback_message = match action {
|
||||
@@ -481,18 +482,18 @@ impl LayoutMessageHandler {
|
||||
diff.iter_mut().for_each(|diff| diff.new_value.apply_keyboard_shortcut(action_input_mapping));
|
||||
|
||||
let message = match layout_target {
|
||||
LayoutTarget::MenuBar => unreachable!("Menu bar is not diffed"),
|
||||
LayoutTarget::DialogButtons => FrontendMessage::UpdateDialogButtons { layout_target, diff },
|
||||
LayoutTarget::DialogColumn1 => FrontendMessage::UpdateDialogColumn1 { layout_target, diff },
|
||||
LayoutTarget::DialogColumn2 => FrontendMessage::UpdateDialogColumn2 { layout_target, diff },
|
||||
LayoutTarget::DocumentBar => FrontendMessage::UpdateDocumentBarLayout { layout_target, diff },
|
||||
LayoutTarget::DocumentMode => FrontendMessage::UpdateDocumentModeLayout { layout_target, diff },
|
||||
LayoutTarget::DataPanel => FrontendMessage::UpdateDataPanelLayout { layout_target, diff },
|
||||
LayoutTarget::LayersPanelControlLeftBar => FrontendMessage::UpdateLayersPanelControlBarLeftLayout { layout_target, diff },
|
||||
LayoutTarget::LayersPanelControlRightBar => FrontendMessage::UpdateLayersPanelControlBarRightLayout { layout_target, diff },
|
||||
LayoutTarget::LayersPanelBottomBar => FrontendMessage::UpdateLayersPanelBottomBarLayout { layout_target, diff },
|
||||
LayoutTarget::MenuBar => unreachable!("Menu bar is not diffed"),
|
||||
LayoutTarget::PropertiesPanel => FrontendMessage::UpdatePropertiesPanelLayout { layout_target, diff },
|
||||
LayoutTarget::NodeGraphControlBar => FrontendMessage::UpdateNodeGraphControlBarLayout { layout_target, diff },
|
||||
LayoutTarget::PropertiesSections => FrontendMessage::UpdatePropertyPanelSectionsLayout { layout_target, diff },
|
||||
LayoutTarget::Spreadsheet => FrontendMessage::UpdateSpreadsheetLayout { layout_target, diff },
|
||||
LayoutTarget::ToolOptions => FrontendMessage::UpdateToolOptionsLayout { layout_target, diff },
|
||||
LayoutTarget::ToolShelf => FrontendMessage::UpdateToolShelfLayout { layout_target, diff },
|
||||
LayoutTarget::WorkingColors => FrontendMessage::UpdateWorkingColorsLayout { layout_target, diff },
|
||||
|
||||
@@ -42,9 +42,9 @@ pub enum LayoutTarget {
|
||||
/// Bar at the top of the node graph containing the location and the "Preview" and "Hide" buttons.
|
||||
NodeGraphControlBar,
|
||||
/// The body of the Properties panel containing many collapsable sections.
|
||||
PropertiesSections,
|
||||
PropertiesPanel,
|
||||
/// The spredsheet panel allows for the visualisation of data in the graph.
|
||||
Spreadsheet,
|
||||
DataPanel,
|
||||
/// The bar directly above the canvas, left-aligned and to the right of the document mode dropdown.
|
||||
ToolOptions,
|
||||
/// The vertical buttons for all of the tools on the left of the canvas.
|
||||
@@ -369,6 +369,7 @@ impl LayoutGroup {
|
||||
Widget::IconButton(x) => &mut x.tooltip,
|
||||
Widget::IconLabel(x) => &mut x.tooltip,
|
||||
Widget::ImageButton(x) => &mut x.tooltip,
|
||||
Widget::ImageLabel(x) => &mut x.tooltip,
|
||||
Widget::NumberInput(x) => &mut x.tooltip,
|
||||
Widget::ParameterExposeButton(x) => &mut x.tooltip,
|
||||
Widget::PopoverButton(x) => &mut x.tooltip,
|
||||
@@ -546,6 +547,7 @@ pub enum Widget {
|
||||
IconButton(IconButton),
|
||||
IconLabel(IconLabel),
|
||||
ImageButton(ImageButton),
|
||||
ImageLabel(ImageLabel),
|
||||
InvisibleStandinInput(InvisibleStandinInput),
|
||||
NodeCatalog(NodeCatalog),
|
||||
NumberInput(NumberInput),
|
||||
@@ -622,6 +624,7 @@ impl DiffUpdate {
|
||||
Widget::TextButton(widget) => Some((&mut widget.tooltip, &mut widget.tooltip_shortcut)),
|
||||
Widget::ImageButton(widget) => Some((&mut widget.tooltip, &mut widget.tooltip_shortcut)),
|
||||
Widget::IconLabel(_)
|
||||
| Widget::ImageLabel(_)
|
||||
| Widget::CurveInput(_)
|
||||
| Widget::InvisibleStandinInput(_)
|
||||
| Widget::NodeCatalog(_)
|
||||
@@ -653,7 +656,7 @@ impl DiffUpdate {
|
||||
};
|
||||
|
||||
match self {
|
||||
Self::SubLayout(sub_layout) => sub_layout.iter_mut().flat_map(|group| group.iter_mut()).for_each(convert_tooltip),
|
||||
Self::SubLayout(sub_layout) => sub_layout.iter_mut().flat_map(|layout_group| layout_group.iter_mut()).for_each(convert_tooltip),
|
||||
Self::LayoutGroup(layout_group) => layout_group.iter_mut().for_each(convert_tooltip),
|
||||
Self::Widget(widget_holder) => convert_tooltip(widget_holder),
|
||||
}
|
||||
|
||||
@@ -168,8 +168,6 @@ pub struct ColorInput {
|
||||
#[widget_builder(constructor)]
|
||||
pub value: FillChoice,
|
||||
|
||||
pub disabled: bool,
|
||||
|
||||
// TODO: Implement
|
||||
// #[serde(rename = "allowTransparency")]
|
||||
// #[derivative(Default(value = "false"))]
|
||||
@@ -179,9 +177,11 @@ pub struct ColorInput {
|
||||
#[derivative(Default(value = "true"))]
|
||||
pub allow_none: bool,
|
||||
|
||||
// TODO: Implement
|
||||
// pub disabled: bool,
|
||||
//
|
||||
pub disabled: bool,
|
||||
|
||||
#[serde(rename = "menuDirection")]
|
||||
pub menu_direction: Option<MenuDirection>,
|
||||
|
||||
pub tooltip: String,
|
||||
|
||||
#[serde(skip)]
|
||||
|
||||
@@ -5,8 +5,6 @@ use graphene_std::Color;
|
||||
use graphene_std::raster::curve::Curve;
|
||||
use graphene_std::transform::ReferencePoint;
|
||||
use graphite_proc_macros::WidgetBuilder;
|
||||
use once_cell::sync::OnceCell;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Clone, Derivative, serde::Serialize, serde::Deserialize, WidgetBuilder, specta::Type)]
|
||||
#[derivative(Debug, PartialEq)]
|
||||
@@ -20,7 +18,7 @@ pub struct CheckboxInput {
|
||||
|
||||
pub tooltip: String,
|
||||
|
||||
#[serde(rename = "forLabel", skip_serializing_if = "checkbox_id_is_empty")]
|
||||
#[serde(rename = "forLabel")]
|
||||
pub for_label: CheckboxId,
|
||||
|
||||
#[serde(skip)]
|
||||
@@ -44,19 +42,24 @@ impl Default for CheckboxInput {
|
||||
icon: "Checkmark".into(),
|
||||
tooltip: Default::default(),
|
||||
tooltip_shortcut: Default::default(),
|
||||
for_label: CheckboxId::default(),
|
||||
for_label: CheckboxId::new(),
|
||||
on_update: Default::default(),
|
||||
on_commit: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Default, Debug, Eq, PartialEq)]
|
||||
pub struct CheckboxId(Arc<OnceCell<u64>>);
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||
pub struct CheckboxId(u64);
|
||||
|
||||
impl CheckboxId {
|
||||
pub fn fill(&mut self) {
|
||||
let _ = self.0.set(graphene_std::uuid::generate_uuid());
|
||||
pub fn new() -> Self {
|
||||
Self(graphene_std::uuid::generate_uuid())
|
||||
}
|
||||
}
|
||||
impl Default for CheckboxId {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
impl specta::Type for CheckboxId {
|
||||
@@ -65,29 +68,6 @@ impl specta::Type for CheckboxId {
|
||||
specta::datatype::DataType::Primitive(specta::datatype::PrimitiveType::u64)
|
||||
}
|
||||
}
|
||||
impl serde::Serialize for CheckboxId {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
self.0.get().copied().serialize(serializer)
|
||||
}
|
||||
}
|
||||
impl<'a> serde::Deserialize<'a> for CheckboxId {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'a>,
|
||||
{
|
||||
let id = u64::deserialize(deserializer)?;
|
||||
let checkbox_id = CheckboxId(OnceCell::new().into());
|
||||
checkbox_id.0.set(id).map_err(serde::de::Error::custom)?;
|
||||
Ok(checkbox_id)
|
||||
}
|
||||
}
|
||||
|
||||
fn checkbox_id_is_empty(id: &CheckboxId) -> bool {
|
||||
id.0.get().is_none()
|
||||
}
|
||||
|
||||
#[derive(Clone, serde::Serialize, serde::Deserialize, Derivative, WidgetBuilder, specta::Type)]
|
||||
#[derivative(Debug, PartialEq, Default)]
|
||||
@@ -436,6 +416,9 @@ pub struct TextInput {
|
||||
#[serde(rename = "minWidth")]
|
||||
pub min_width: u32,
|
||||
|
||||
#[serde(rename = "maxWidth")]
|
||||
pub max_width: u32,
|
||||
|
||||
// Callbacks
|
||||
#[serde(skip)]
|
||||
#[derivative(Debug = "ignore", PartialEq = "ignore")]
|
||||
|
||||
@@ -44,34 +44,40 @@ pub struct TextLabel {
|
||||
|
||||
pub italic: bool,
|
||||
|
||||
pub monospace: bool,
|
||||
|
||||
pub multiline: bool,
|
||||
|
||||
#[serde(rename = "centerAlign")]
|
||||
pub center_align: bool,
|
||||
|
||||
#[serde(rename = "tableAlign")]
|
||||
pub table_align: bool,
|
||||
|
||||
pub multiline: bool,
|
||||
|
||||
#[serde(rename = "minWidth")]
|
||||
pub min_width: u32,
|
||||
pub min_width: String,
|
||||
|
||||
pub tooltip: String,
|
||||
|
||||
#[serde(rename = "checkboxId")]
|
||||
#[widget_builder(skip)]
|
||||
pub checkbox_id: CheckboxId,
|
||||
#[serde(rename = "forCheckbox")]
|
||||
pub for_checkbox: CheckboxId,
|
||||
|
||||
// Body
|
||||
#[widget_builder(constructor)]
|
||||
pub value: String,
|
||||
}
|
||||
|
||||
impl TextLabel {
|
||||
pub fn for_checkbox(mut self, id: &mut CheckboxId) -> Self {
|
||||
id.fill();
|
||||
self.checkbox_id = id.clone();
|
||||
self
|
||||
}
|
||||
#[derive(Clone, serde::Serialize, serde::Deserialize, Derivative, Default, WidgetBuilder, specta::Type)]
|
||||
#[derivative(Debug, PartialEq)]
|
||||
pub struct ImageLabel {
|
||||
#[widget_builder(constructor)]
|
||||
pub url: String,
|
||||
|
||||
pub width: Option<String>,
|
||||
|
||||
pub height: Option<String>,
|
||||
|
||||
pub tooltip: String,
|
||||
}
|
||||
|
||||
// TODO: Add UserInputLabel
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
use crate::messages::prelude::*;
|
||||
use graphene_std::renderer::RenderMetadata;
|
||||
use graphite_proc_macros::*;
|
||||
|
||||
#[impl_message]
|
||||
@@ -9,10 +8,14 @@ pub enum Message {
|
||||
#[child]
|
||||
Animation(AnimationMessage),
|
||||
#[child]
|
||||
AppWindow(AppWindowMessage),
|
||||
#[child]
|
||||
Broadcast(BroadcastMessage),
|
||||
#[child]
|
||||
Debug(DebugMessage),
|
||||
#[child]
|
||||
Defer(DeferMessage),
|
||||
#[child]
|
||||
Dialog(DialogMessage),
|
||||
#[child]
|
||||
Frontend(FrontendMessage),
|
||||
@@ -30,18 +33,12 @@ pub enum Message {
|
||||
Preferences(PreferencesMessage),
|
||||
#[child]
|
||||
Tool(ToolMessage),
|
||||
#[child]
|
||||
Workspace(WorkspaceMessage),
|
||||
|
||||
// Messages
|
||||
NoOp,
|
||||
Batched {
|
||||
messages: Box<[Message]>,
|
||||
},
|
||||
StartBuffer,
|
||||
EndBuffer {
|
||||
render_metadata: RenderMetadata,
|
||||
},
|
||||
NoOp,
|
||||
}
|
||||
|
||||
/// Provides an impl of `specta::Type` for `MessageDiscriminant`, the struct created by `impl_message`.
|
||||
@@ -72,14 +69,12 @@ mod test {
|
||||
|
||||
fn print_tree_node(tree: &DebugMessageTree, prefix: &str, is_last: bool, file: &mut std::fs::File) {
|
||||
// Print the current node
|
||||
let (branch, child_prefix) = if tree.has_message_handler_data_fields() || tree.has_message_handler_fields() {
|
||||
("├── ", format!("{}│ ", prefix))
|
||||
let (branch, child_prefix) = if tree.message_handler_data_fields().is_some() || tree.message_handler_fields().is_some() {
|
||||
("├── ", format!("{prefix}│ "))
|
||||
} else if is_last {
|
||||
("└── ", format!("{prefix} "))
|
||||
} else {
|
||||
if is_last {
|
||||
("└── ", format!("{} ", prefix))
|
||||
} else {
|
||||
("├── ", format!("{}│ ", prefix))
|
||||
}
|
||||
("├── ", format!("{prefix}│ "))
|
||||
};
|
||||
|
||||
if tree.path().is_empty() {
|
||||
@@ -97,24 +92,38 @@ mod test {
|
||||
}
|
||||
}
|
||||
|
||||
// Print handler field if any
|
||||
if let Some(data) = tree.message_handler_fields() {
|
||||
let len = data.fields().len();
|
||||
let (branch, child_prefix) = if tree.has_message_handler_data_fields() {
|
||||
("├── ", format!("{}│ ", prefix))
|
||||
} else {
|
||||
("└── ", format!("{} ", prefix))
|
||||
};
|
||||
if data.path().is_empty() {
|
||||
file.write_all(format!("{}{}{}\n", prefix, branch, data.name()).as_bytes()).unwrap();
|
||||
} else {
|
||||
file.write_all(format!("{}{}{} `{}`\n", prefix, branch, data.name(), data.path()).as_bytes()).unwrap();
|
||||
}
|
||||
for (i, field) in data.fields().iter().enumerate() {
|
||||
// Print message field if any
|
||||
if let Some(fields) = tree.fields() {
|
||||
let len = fields.len();
|
||||
for (i, field) in fields.iter().enumerate() {
|
||||
let is_last_field = i == len - 1;
|
||||
let branch = if is_last_field { "└── " } else { "├── " };
|
||||
|
||||
file.write_all(format!("{}{}{}\n", child_prefix, branch, field.0).as_bytes()).unwrap();
|
||||
file.write_all(format!("{child_prefix}{branch}{field}\n").as_bytes()).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
// Print handler field if any
|
||||
if let Some(data) = tree.message_handler_fields() {
|
||||
let len = data.fields().len();
|
||||
let (branch, child_prefix) = if tree.message_handler_data_fields().is_some() {
|
||||
("├── ", format!("{prefix}│ "))
|
||||
} else {
|
||||
("└── ", format!("{prefix} "))
|
||||
};
|
||||
|
||||
const FRONTEND_MESSAGE_STR: &str = "FrontendMessage";
|
||||
if data.name().is_empty() && tree.name() != FRONTEND_MESSAGE_STR {
|
||||
panic!("{}'s MessageHandler is missing #[message_handler_data]", tree.name());
|
||||
} else if tree.name() != FRONTEND_MESSAGE_STR {
|
||||
file.write_all(format!("{}{}{} `{}`\n", prefix, branch, data.name(), data.path()).as_bytes()).unwrap();
|
||||
|
||||
for (i, field) in data.fields().iter().enumerate() {
|
||||
let is_last_field = i == len - 1;
|
||||
let branch = if is_last_field { "└── " } else { "├── " };
|
||||
|
||||
file.write_all(format!("{}{}{}\n", child_prefix, branch, field.0).as_bytes()).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
//! The root-level messages forming the first layer of the message system architecture.
|
||||
|
||||
pub mod animation;
|
||||
pub mod app_window;
|
||||
pub mod broadcast;
|
||||
pub mod debug;
|
||||
pub mod defer;
|
||||
pub mod dialog;
|
||||
pub mod frontend;
|
||||
pub mod globals;
|
||||
@@ -14,4 +16,3 @@ pub mod portfolio;
|
||||
pub mod preferences;
|
||||
pub mod prelude;
|
||||
pub mod tool;
|
||||
pub mod workspace;
|
||||
|
||||
+10
-10
@@ -1,32 +1,32 @@
|
||||
use crate::messages::prelude::*;
|
||||
use crate::node_graph_executor::InspectResult;
|
||||
|
||||
/// The spreadsheet UI allows for instance data to be previewed.
|
||||
#[impl_message(Message, PortfolioMessage, Spreadsheet)]
|
||||
/// The Data panel UI allows the user to visualize the output data of the selected node.
|
||||
#[impl_message(Message, DocumentMessage, DataPanel)]
|
||||
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
|
||||
pub enum SpreadsheetMessage {
|
||||
ToggleOpen,
|
||||
|
||||
pub enum DataPanelMessage {
|
||||
UpdateLayout {
|
||||
#[serde(skip)]
|
||||
inspect_result: InspectResult,
|
||||
},
|
||||
ClearLayout,
|
||||
|
||||
PushToInstancePath {
|
||||
PushToElementPath {
|
||||
index: usize,
|
||||
},
|
||||
TruncateInstancePath {
|
||||
TruncateElementPath {
|
||||
len: usize,
|
||||
},
|
||||
|
||||
ViewVectorDataDomain {
|
||||
domain: VectorDataDomain,
|
||||
ViewVectorTableTab {
|
||||
tab: VectorTableTab,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Clone, Copy, Default, Debug, serde::Serialize, serde::Deserialize)]
|
||||
pub enum VectorDataDomain {
|
||||
pub enum VectorTableTab {
|
||||
#[default]
|
||||
Properties,
|
||||
Points,
|
||||
Segments,
|
||||
Regions,
|
||||
@@ -0,0 +1,655 @@
|
||||
use super::VectorTableTab;
|
||||
use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, LayoutTarget, WidgetLayout};
|
||||
use crate::messages::portfolio::document::data_panel::DataPanelMessage;
|
||||
use crate::messages::portfolio::document::utility_types::network_interface::NodeNetworkInterface;
|
||||
use crate::messages::prelude::*;
|
||||
use crate::messages::tool::tool_messages::tool_prelude::*;
|
||||
use glam::{Affine2, Vec2};
|
||||
use graph_craft::document::NodeId;
|
||||
use graphene_std::Color;
|
||||
use graphene_std::Context;
|
||||
use graphene_std::gradient::GradientStops;
|
||||
use graphene_std::memo::IORecord;
|
||||
use graphene_std::raster_types::{CPU, GPU, Raster};
|
||||
use graphene_std::table::Table;
|
||||
use graphene_std::vector::Vector;
|
||||
use graphene_std::vector::style::{Fill, FillChoice};
|
||||
use graphene_std::{Artboard, Graphic};
|
||||
use std::any::Any;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(ExtractField)]
|
||||
pub struct DataPanelMessageContext<'a> {
|
||||
pub network_interface: &'a mut NodeNetworkInterface,
|
||||
pub data_panel_open: bool,
|
||||
}
|
||||
|
||||
/// The data panel allows for graph data to be previewed.
|
||||
#[derive(Default, Debug, Clone, ExtractField)]
|
||||
pub struct DataPanelMessageHandler {
|
||||
introspected_node: Option<NodeId>,
|
||||
introspected_data: Option<Arc<dyn Any + Send + Sync>>,
|
||||
element_path: Vec<usize>,
|
||||
active_vector_table_tab: VectorTableTab,
|
||||
}
|
||||
|
||||
#[message_handler_data]
|
||||
impl MessageHandler<DataPanelMessage, DataPanelMessageContext<'_>> for DataPanelMessageHandler {
|
||||
fn process_message(&mut self, message: DataPanelMessage, responses: &mut VecDeque<Message>, context: DataPanelMessageContext) {
|
||||
match message {
|
||||
DataPanelMessage::UpdateLayout { mut inspect_result } => {
|
||||
self.introspected_node = Some(inspect_result.inspect_node);
|
||||
self.introspected_data = inspect_result.take_data();
|
||||
self.update_layout(responses, context);
|
||||
}
|
||||
DataPanelMessage::ClearLayout => {
|
||||
self.introspected_node = None;
|
||||
self.introspected_data = None;
|
||||
self.element_path.clear();
|
||||
self.active_vector_table_tab = VectorTableTab::default();
|
||||
self.update_layout(responses, context);
|
||||
}
|
||||
|
||||
DataPanelMessage::PushToElementPath { index } => {
|
||||
self.element_path.push(index);
|
||||
self.update_layout(responses, context);
|
||||
}
|
||||
DataPanelMessage::TruncateElementPath { len } => {
|
||||
self.element_path.truncate(len);
|
||||
self.update_layout(responses, context);
|
||||
}
|
||||
|
||||
DataPanelMessage::ViewVectorTableTab { tab } => {
|
||||
self.active_vector_table_tab = tab;
|
||||
self.update_layout(responses, context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn actions(&self) -> ActionList {
|
||||
actions!(DataPanelMessage;)
|
||||
}
|
||||
}
|
||||
|
||||
impl DataPanelMessageHandler {
|
||||
fn update_layout(&mut self, responses: &mut VecDeque<Message>, context: DataPanelMessageContext<'_>) {
|
||||
let DataPanelMessageContext { network_interface, .. } = context;
|
||||
|
||||
let mut layout_data = LayoutData {
|
||||
current_depth: 0,
|
||||
desired_path: &mut self.element_path,
|
||||
breadcrumbs: Vec::new(),
|
||||
vector_table_tab: self.active_vector_table_tab,
|
||||
};
|
||||
|
||||
// Main data visualization
|
||||
let mut layout = self
|
||||
.introspected_data
|
||||
.as_ref()
|
||||
.map(|instrospected_data| generate_layout(instrospected_data, &mut layout_data).unwrap_or_else(|| label("Visualization of this data type is not yet supported")))
|
||||
.unwrap_or_default();
|
||||
|
||||
let mut widgets = Vec::new();
|
||||
|
||||
// Selected layer/node name
|
||||
if let Some(node_id) = self.introspected_node {
|
||||
let is_layer = network_interface.is_layer(&node_id, &[]);
|
||||
|
||||
widgets.extend([
|
||||
if is_layer {
|
||||
IconLabel::new("Layer").tooltip("Name of the selected layer").widget_holder()
|
||||
} else {
|
||||
IconLabel::new("Node").tooltip("Name of the selected node").widget_holder()
|
||||
},
|
||||
Separator::new(SeparatorType::Related).widget_holder(),
|
||||
TextInput::new(network_interface.display_name(&node_id, &[]))
|
||||
.tooltip(if is_layer { "Name of the selected layer" } else { "Name of the selected node" })
|
||||
.on_update(move |text_input| {
|
||||
NodeGraphMessage::SetDisplayName {
|
||||
node_id,
|
||||
alias: text_input.value.clone(),
|
||||
skip_adding_history_step: false,
|
||||
}
|
||||
.into()
|
||||
})
|
||||
.max_width(200)
|
||||
.widget_holder(),
|
||||
Separator::new(SeparatorType::Unrelated).widget_holder(),
|
||||
]);
|
||||
}
|
||||
|
||||
// Element path breadcrumbs
|
||||
if !layout_data.breadcrumbs.is_empty() {
|
||||
let breadcrumb = BreadcrumbTrailButtons::new(layout_data.breadcrumbs)
|
||||
.on_update(|&len| DataPanelMessage::TruncateElementPath { len: len as usize }.into())
|
||||
.widget_holder();
|
||||
widgets.push(breadcrumb);
|
||||
}
|
||||
|
||||
if !widgets.is_empty() {
|
||||
layout.insert(0, LayoutGroup::Row { widgets });
|
||||
}
|
||||
|
||||
responses.add(LayoutMessage::SendLayout {
|
||||
layout: Layout::WidgetLayout(WidgetLayout { layout }),
|
||||
layout_target: LayoutTarget::DataPanel,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
struct LayoutData<'a> {
|
||||
current_depth: usize,
|
||||
desired_path: &'a mut Vec<usize>,
|
||||
breadcrumbs: Vec<String>,
|
||||
vector_table_tab: VectorTableTab,
|
||||
}
|
||||
|
||||
macro_rules! generate_layout_downcast {
|
||||
($introspected_data:expr, $data:expr, [ $($ty:ty),* $(,)? ]) => {
|
||||
if false { None }
|
||||
$(
|
||||
else if let Some(io) = $introspected_data.downcast_ref::<IORecord<Context, $ty>>() {
|
||||
Some(io.output.layout_with_breadcrumb($data))
|
||||
}
|
||||
)*
|
||||
else { None }
|
||||
}
|
||||
}
|
||||
// TODO: We simply try all these types sequentially. Find a better strategy.
|
||||
fn generate_layout(introspected_data: &Arc<dyn std::any::Any + Send + Sync + 'static>, data: &mut LayoutData) -> Option<Vec<LayoutGroup>> {
|
||||
generate_layout_downcast!(introspected_data, data, [
|
||||
Table<Artboard>,
|
||||
Table<Graphic>,
|
||||
Table<Vector>,
|
||||
Table<Raster<CPU>>,
|
||||
Table<Raster<GPU>>,
|
||||
Table<Color>,
|
||||
Table<GradientStops>,
|
||||
f64,
|
||||
u32,
|
||||
u64,
|
||||
bool,
|
||||
String,
|
||||
Option<f64>,
|
||||
DVec2,
|
||||
DAffine2,
|
||||
])
|
||||
}
|
||||
|
||||
fn column_headings(value: &[&str]) -> Vec<WidgetHolder> {
|
||||
value.iter().map(|text| TextLabel::new(*text).widget_holder()).collect()
|
||||
}
|
||||
|
||||
fn label(x: impl Into<String>) -> Vec<LayoutGroup> {
|
||||
let error = vec![TextLabel::new(x).widget_holder()];
|
||||
vec![LayoutGroup::Row { widgets: error }]
|
||||
}
|
||||
|
||||
trait TableRowLayout {
|
||||
fn type_name() -> &'static str;
|
||||
fn identifier(&self) -> String;
|
||||
fn layout_with_breadcrumb(&self, data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||
data.breadcrumbs.push(self.identifier());
|
||||
self.element_page(data)
|
||||
}
|
||||
fn element_widget(&self, index: usize) -> WidgetHolder {
|
||||
TextButton::new(self.identifier())
|
||||
.on_update(move |_| DataPanelMessage::PushToElementPath { index }.into())
|
||||
.widget_holder()
|
||||
}
|
||||
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: TableRowLayout> TableRowLayout for Table<T> {
|
||||
fn type_name() -> &'static str {
|
||||
"Table"
|
||||
}
|
||||
fn identifier(&self) -> String {
|
||||
format!("Table<{}> ({} row{})", T::type_name(), self.len(), if self.len() == 1 { "" } else { "s" })
|
||||
}
|
||||
fn element_page(&self, data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||
if let Some(index) = data.desired_path.get(data.current_depth).copied() {
|
||||
if let Some(row) = self.get(index) {
|
||||
data.current_depth += 1;
|
||||
let result = row.element.layout_with_breadcrumb(data);
|
||||
data.current_depth -= 1;
|
||||
return result;
|
||||
} else {
|
||||
warn!("Desired path truncated");
|
||||
data.desired_path.truncate(data.current_depth);
|
||||
}
|
||||
}
|
||||
|
||||
let mut rows = self
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(index, row)| {
|
||||
vec![
|
||||
TextLabel::new(format!("{index}")).widget_holder(),
|
||||
row.element.element_widget(index),
|
||||
TextLabel::new(format_transform_matrix(row.transform)).widget_holder(),
|
||||
TextLabel::new(format!("{}", row.alpha_blending)).widget_holder(),
|
||||
TextLabel::new(row.source_node_id.map_or_else(|| "-".to_string(), |id| format!("{}", id.0))).widget_holder(),
|
||||
]
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
rows.insert(0, column_headings(&["", "element", "transform", "alpha_blending", "source_node_id"]));
|
||||
|
||||
vec![LayoutGroup::Table { rows }]
|
||||
}
|
||||
}
|
||||
|
||||
impl TableRowLayout for Artboard {
|
||||
fn type_name() -> &'static str {
|
||||
"Artboard"
|
||||
}
|
||||
fn identifier(&self) -> String {
|
||||
self.label.clone()
|
||||
}
|
||||
fn element_page(&self, data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||
self.content.element_page(data)
|
||||
}
|
||||
}
|
||||
|
||||
impl TableRowLayout for Graphic {
|
||||
fn type_name() -> &'static str {
|
||||
"Graphic"
|
||||
}
|
||||
fn identifier(&self) -> String {
|
||||
match self {
|
||||
Self::Graphic(table) => table.identifier(),
|
||||
Self::Vector(table) => table.identifier(),
|
||||
Self::RasterCPU(table) => table.identifier(),
|
||||
Self::RasterGPU(table) => table.identifier(),
|
||||
Self::Color(table) => table.identifier(),
|
||||
Self::Gradient(table) => table.identifier(),
|
||||
}
|
||||
}
|
||||
// Don't put a breadcrumb for Graphic
|
||||
fn layout_with_breadcrumb(&self, data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||
self.element_page(data)
|
||||
}
|
||||
fn element_page(&self, data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||
match self {
|
||||
Self::Graphic(table) => table.layout_with_breadcrumb(data),
|
||||
Self::Vector(table) => table.layout_with_breadcrumb(data),
|
||||
Self::RasterCPU(table) => table.layout_with_breadcrumb(data),
|
||||
Self::RasterGPU(table) => table.layout_with_breadcrumb(data),
|
||||
Self::Color(table) => table.layout_with_breadcrumb(data),
|
||||
Self::Gradient(table) => table.layout_with_breadcrumb(data),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TableRowLayout for Vector {
|
||||
fn type_name() -> &'static str {
|
||||
"Vector"
|
||||
}
|
||||
fn identifier(&self) -> String {
|
||||
format!(
|
||||
"Vector ({} point{}, {} segment{})",
|
||||
self.point_domain.ids().len(),
|
||||
if self.point_domain.ids().len() == 1 { "" } else { "s" },
|
||||
self.segment_domain.ids().len(),
|
||||
if self.segment_domain.ids().len() == 1 { "" } else { "s" }
|
||||
)
|
||||
}
|
||||
fn element_page(&self, data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||
let table_tab_entries = [VectorTableTab::Properties, VectorTableTab::Points, VectorTableTab::Segments, VectorTableTab::Regions]
|
||||
.into_iter()
|
||||
.map(|tab| {
|
||||
RadioEntryData::new(format!("{tab:?}"))
|
||||
.label(format!("{tab:?}"))
|
||||
.on_update(move |_| DataPanelMessage::ViewVectorTableTab { tab }.into())
|
||||
})
|
||||
.collect();
|
||||
let table_tabs = vec![RadioInput::new(table_tab_entries).selected_index(Some(data.vector_table_tab as u32)).widget_holder()];
|
||||
|
||||
let mut table_rows = Vec::new();
|
||||
match data.vector_table_tab {
|
||||
VectorTableTab::Properties => {
|
||||
table_rows.push(column_headings(&["property", "value"]));
|
||||
|
||||
match self.style.fill.clone() {
|
||||
Fill::None => table_rows.push(vec![
|
||||
TextLabel::new("Fill").widget_holder(),
|
||||
ColorInput::new(FillChoice::None).disabled(true).menu_direction(Some(MenuDirection::Top)).widget_holder(),
|
||||
]),
|
||||
Fill::Solid(color) => table_rows.push(vec![
|
||||
TextLabel::new("Fill").widget_holder(),
|
||||
ColorInput::new(FillChoice::Solid(color)).disabled(true).menu_direction(Some(MenuDirection::Top)).widget_holder(),
|
||||
]),
|
||||
Fill::Gradient(gradient) => {
|
||||
table_rows.push(vec![
|
||||
TextLabel::new("Fill").widget_holder(),
|
||||
ColorInput::new(FillChoice::Gradient(gradient.stops))
|
||||
.disabled(true)
|
||||
.menu_direction(Some(MenuDirection::Top))
|
||||
.widget_holder(),
|
||||
]);
|
||||
table_rows.push(vec![
|
||||
TextLabel::new("Fill Gradient Type").widget_holder(),
|
||||
TextLabel::new(gradient.gradient_type.to_string()).widget_holder(),
|
||||
]);
|
||||
table_rows.push(vec![
|
||||
TextLabel::new("Fill Gradient Start").widget_holder(),
|
||||
TextLabel::new(format_dvec2(gradient.start)).widget_holder(),
|
||||
]);
|
||||
table_rows.push(vec![TextLabel::new("Fill Gradient End").widget_holder(), TextLabel::new(format_dvec2(gradient.end)).widget_holder()]);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(stroke) = self.style.stroke.clone() {
|
||||
let color = if let Some(color) = stroke.color { FillChoice::Solid(color) } else { FillChoice::None };
|
||||
table_rows.push(vec![
|
||||
TextLabel::new("Stroke").widget_holder(),
|
||||
ColorInput::new(color).disabled(true).menu_direction(Some(MenuDirection::Top)).widget_holder(),
|
||||
]);
|
||||
table_rows.push(vec![TextLabel::new("Stroke Weight").widget_holder(), TextLabel::new(format!("{} px", stroke.weight)).widget_holder()]);
|
||||
table_rows.push(vec![
|
||||
TextLabel::new("Stroke Dash Lengths").widget_holder(),
|
||||
TextLabel::new(if stroke.dash_lengths.is_empty() {
|
||||
"-".to_string()
|
||||
} else {
|
||||
format!("[{}]", stroke.dash_lengths.iter().map(|x| format!("{x} px")).collect::<Vec<_>>().join(", "))
|
||||
})
|
||||
.widget_holder(),
|
||||
]);
|
||||
table_rows.push(vec![
|
||||
TextLabel::new("Stroke Dash Offset").widget_holder(),
|
||||
TextLabel::new(format!("{}", stroke.dash_offset)).widget_holder(),
|
||||
]);
|
||||
table_rows.push(vec![TextLabel::new("Stroke Cap").widget_holder(), TextLabel::new(stroke.cap.to_string()).widget_holder()]);
|
||||
table_rows.push(vec![TextLabel::new("Stroke Join").widget_holder(), TextLabel::new(stroke.join.to_string()).widget_holder()]);
|
||||
table_rows.push(vec![
|
||||
TextLabel::new("Stroke Join Miter Limit").widget_holder(),
|
||||
TextLabel::new(format!("{}", stroke.join_miter_limit)).widget_holder(),
|
||||
]);
|
||||
table_rows.push(vec![TextLabel::new("Stroke Align").widget_holder(), TextLabel::new(stroke.align.to_string()).widget_holder()]);
|
||||
table_rows.push(vec![
|
||||
TextLabel::new("Stroke Transform").widget_holder(),
|
||||
TextLabel::new(format_transform_matrix(&stroke.transform)).widget_holder(),
|
||||
]);
|
||||
table_rows.push(vec![
|
||||
TextLabel::new("Stroke Non-Scaling").widget_holder(),
|
||||
TextLabel::new((if stroke.non_scaling { "Yes" } else { "No" }).to_string()).widget_holder(),
|
||||
]);
|
||||
table_rows.push(vec![
|
||||
TextLabel::new("Stroke Paint Order").widget_holder(),
|
||||
TextLabel::new(stroke.paint_order.to_string()).widget_holder(),
|
||||
]);
|
||||
}
|
||||
|
||||
let colinear = self.colinear_manipulators.iter().map(|[a, b]| format!("[{a} / {b}]")).collect::<Vec<_>>().join(", ");
|
||||
let colinear = if colinear.is_empty() { "-".to_string() } else { colinear };
|
||||
table_rows.push(vec![TextLabel::new("Colinear Handle IDs").widget_holder(), TextLabel::new(colinear).widget_holder()]);
|
||||
|
||||
table_rows.push(vec![
|
||||
TextLabel::new("Upstream Nested Layers").widget_holder(),
|
||||
TextLabel::new(if self.upstream_nested_layers.is_some() {
|
||||
"Yes (this preserves references to its upstream nested layers for editing by tools)"
|
||||
} else {
|
||||
"No (this doesn't preserve references to its upstream nested layers for editing by tools)"
|
||||
})
|
||||
.widget_holder(),
|
||||
]);
|
||||
}
|
||||
VectorTableTab::Points => {
|
||||
table_rows.push(column_headings(&["", "position"]));
|
||||
table_rows.extend(
|
||||
self.point_domain
|
||||
.iter()
|
||||
.map(|(id, position)| vec![TextLabel::new(format!("{}", id.inner())).widget_holder(), TextLabel::new(format!("{position}")).widget_holder()]),
|
||||
);
|
||||
}
|
||||
VectorTableTab::Segments => {
|
||||
table_rows.push(column_headings(&["", "start_index", "end_index", "handles"]));
|
||||
table_rows.extend(self.segment_domain.iter().map(|(id, start, end, handles)| {
|
||||
vec![
|
||||
TextLabel::new(format!("{}", id.inner())).widget_holder(),
|
||||
TextLabel::new(format!("{start}")).widget_holder(),
|
||||
TextLabel::new(format!("{end}")).widget_holder(),
|
||||
TextLabel::new(format!("{handles:?}")).widget_holder(),
|
||||
]
|
||||
}));
|
||||
}
|
||||
VectorTableTab::Regions => {
|
||||
table_rows.push(column_headings(&["", "segment_range", "fill"]));
|
||||
table_rows.extend(self.region_domain.iter().map(|(id, segment_range, fill)| {
|
||||
vec![
|
||||
TextLabel::new(format!("{}", id.inner())).widget_holder(),
|
||||
TextLabel::new(format!("{segment_range:?}")).widget_holder(),
|
||||
TextLabel::new(format!("{}", fill.inner())).widget_holder(),
|
||||
]
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
vec![LayoutGroup::Row { widgets: table_tabs }, LayoutGroup::Table { rows: table_rows }]
|
||||
}
|
||||
}
|
||||
|
||||
impl TableRowLayout for Raster<CPU> {
|
||||
fn type_name() -> &'static str {
|
||||
"Raster"
|
||||
}
|
||||
fn identifier(&self) -> String {
|
||||
format!("Raster ({}x{})", self.width, self.height)
|
||||
}
|
||||
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||
let base64_string = self.data().base64_string.clone().unwrap_or_else(|| {
|
||||
use base64::Engine;
|
||||
|
||||
let output = self.data().to_png();
|
||||
let preamble = "data:image/png;base64,";
|
||||
let mut base64_string = String::with_capacity(preamble.len() + output.len() * 4);
|
||||
base64_string.push_str(preamble);
|
||||
base64::engine::general_purpose::STANDARD.encode_string(output, &mut base64_string);
|
||||
base64_string
|
||||
});
|
||||
|
||||
let widgets = vec![ImageLabel::new(base64_string).widget_holder()];
|
||||
vec![LayoutGroup::Row { widgets }]
|
||||
}
|
||||
}
|
||||
|
||||
impl TableRowLayout for Raster<GPU> {
|
||||
fn type_name() -> &'static str {
|
||||
"Raster"
|
||||
}
|
||||
fn identifier(&self) -> String {
|
||||
format!("Raster ({}x{})", self.data().width(), self.data().height())
|
||||
}
|
||||
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||
let widgets = vec![TextLabel::new("Raster is a texture on the GPU and cannot currently be displayed here").widget_holder()];
|
||||
vec![LayoutGroup::Row { widgets }]
|
||||
}
|
||||
}
|
||||
|
||||
impl TableRowLayout for Color {
|
||||
fn type_name() -> &'static str {
|
||||
"Color"
|
||||
}
|
||||
fn identifier(&self) -> String {
|
||||
format!("Color (#{})", self.to_gamma_srgb().to_rgba_hex_srgb())
|
||||
}
|
||||
fn element_widget(&self, _index: usize) -> WidgetHolder {
|
||||
ColorInput::new(FillChoice::Solid(*self)).disabled(true).menu_direction(Some(MenuDirection::Top)).widget_holder()
|
||||
}
|
||||
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||
let widgets = vec![self.element_widget(0)];
|
||||
vec![LayoutGroup::Row { widgets }]
|
||||
}
|
||||
}
|
||||
|
||||
impl TableRowLayout for GradientStops {
|
||||
fn type_name() -> &'static str {
|
||||
"Gradient"
|
||||
}
|
||||
fn identifier(&self) -> String {
|
||||
format!("Gradient ({} stops)", self.0.len())
|
||||
}
|
||||
fn element_widget(&self, _index: usize) -> WidgetHolder {
|
||||
ColorInput::new(FillChoice::Gradient(self.clone()))
|
||||
.disabled(true)
|
||||
.menu_direction(Some(MenuDirection::Top))
|
||||
.widget_holder()
|
||||
}
|
||||
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||
let widgets = vec![self.element_widget(0)];
|
||||
vec![LayoutGroup::Row { widgets }]
|
||||
}
|
||||
}
|
||||
|
||||
impl TableRowLayout for f64 {
|
||||
fn type_name() -> &'static str {
|
||||
"Number (f64)"
|
||||
}
|
||||
fn identifier(&self) -> String {
|
||||
"Number (f64)".to_string()
|
||||
}
|
||||
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||
let widgets = vec![TextLabel::new(self.to_string()).widget_holder()];
|
||||
vec![LayoutGroup::Row { widgets }]
|
||||
}
|
||||
}
|
||||
|
||||
impl TableRowLayout for u32 {
|
||||
fn type_name() -> &'static str {
|
||||
"Number (u32)"
|
||||
}
|
||||
fn identifier(&self) -> String {
|
||||
"Number (u32)".to_string()
|
||||
}
|
||||
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||
let widgets = vec![TextLabel::new(self.to_string()).widget_holder()];
|
||||
vec![LayoutGroup::Row { widgets }]
|
||||
}
|
||||
}
|
||||
|
||||
impl TableRowLayout for u64 {
|
||||
fn type_name() -> &'static str {
|
||||
"Number (u64)"
|
||||
}
|
||||
fn identifier(&self) -> String {
|
||||
"Number (u64)".to_string()
|
||||
}
|
||||
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||
let widgets = vec![TextLabel::new(self.to_string()).widget_holder()];
|
||||
vec![LayoutGroup::Row { widgets }]
|
||||
}
|
||||
}
|
||||
|
||||
impl TableRowLayout for bool {
|
||||
fn type_name() -> &'static str {
|
||||
"Bool"
|
||||
}
|
||||
fn identifier(&self) -> String {
|
||||
"Bool".to_string()
|
||||
}
|
||||
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||
let widgets = vec![TextLabel::new(self.to_string()).widget_holder()];
|
||||
vec![LayoutGroup::Row { widgets }]
|
||||
}
|
||||
}
|
||||
|
||||
impl TableRowLayout for String {
|
||||
fn type_name() -> &'static str {
|
||||
"String"
|
||||
}
|
||||
fn identifier(&self) -> String {
|
||||
"String".to_string()
|
||||
}
|
||||
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||
let widgets = vec![TextAreaInput::new(self.to_string()).disabled(true).widget_holder()];
|
||||
vec![LayoutGroup::Row { widgets }]
|
||||
}
|
||||
}
|
||||
|
||||
impl TableRowLayout for Option<f64> {
|
||||
fn type_name() -> &'static str {
|
||||
"Option<f64>"
|
||||
}
|
||||
fn identifier(&self) -> String {
|
||||
"Option<f64>".to_string()
|
||||
}
|
||||
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||
let widgets = vec![TextLabel::new(format!("{self:?}")).widget_holder()];
|
||||
vec![LayoutGroup::Row { widgets }]
|
||||
}
|
||||
}
|
||||
|
||||
impl TableRowLayout for DVec2 {
|
||||
fn type_name() -> &'static str {
|
||||
"Vec2"
|
||||
}
|
||||
fn identifier(&self) -> String {
|
||||
"Vec2".to_string()
|
||||
}
|
||||
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||
let widgets = vec![TextLabel::new(format!("({}, {})", self.x, self.y)).widget_holder()];
|
||||
vec![LayoutGroup::Row { widgets }]
|
||||
}
|
||||
}
|
||||
|
||||
impl TableRowLayout for Vec2 {
|
||||
fn type_name() -> &'static str {
|
||||
"Vec2"
|
||||
}
|
||||
fn identifier(&self) -> String {
|
||||
"Vec2".to_string()
|
||||
}
|
||||
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||
let widgets = vec![TextLabel::new(format!("({}, {})", self.x, self.y)).widget_holder()];
|
||||
vec![LayoutGroup::Row { widgets }]
|
||||
}
|
||||
}
|
||||
|
||||
impl TableRowLayout for DAffine2 {
|
||||
fn type_name() -> &'static str {
|
||||
"Transform"
|
||||
}
|
||||
fn identifier(&self) -> String {
|
||||
"Transform".to_string()
|
||||
}
|
||||
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||
let widgets = vec![TextLabel::new(format_transform_matrix(self)).widget_holder()];
|
||||
vec![LayoutGroup::Row { widgets }]
|
||||
}
|
||||
}
|
||||
|
||||
impl TableRowLayout for Affine2 {
|
||||
fn type_name() -> &'static str {
|
||||
"Transform"
|
||||
}
|
||||
fn identifier(&self) -> String {
|
||||
"Transform".to_string()
|
||||
}
|
||||
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||
let matrix = DAffine2::from_cols_array(&self.to_cols_array().map(|x| x as f64));
|
||||
let widgets = vec![TextLabel::new(format_transform_matrix(&matrix)).widget_holder()];
|
||||
vec![LayoutGroup::Row { widgets }]
|
||||
}
|
||||
}
|
||||
|
||||
fn format_transform_matrix(transform: &DAffine2) -> String {
|
||||
let (scale, angle, translation) = transform.to_scale_angle_translation();
|
||||
let rotation = if angle == -0. { 0. } else { angle.to_degrees() };
|
||||
let round = |x: f64| (x * 1e3).round() / 1e3;
|
||||
|
||||
format!(
|
||||
"Location: ({} px, {} px) — Rotation: {rotation:2}° — Scale: ({}x, {}x)",
|
||||
round(translation.x),
|
||||
round(translation.y),
|
||||
round(scale.x),
|
||||
round(scale.y)
|
||||
)
|
||||
}
|
||||
|
||||
fn format_dvec2(value: DVec2) -> String {
|
||||
let round = |x: f64| (x * 1e3).round() / 1e3;
|
||||
format!("({} px, {} px)", round(value.x), round(value.y))
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
mod data_panel_message;
|
||||
mod data_panel_message_handler;
|
||||
|
||||
#[doc(inline)]
|
||||
pub use data_panel_message::*;
|
||||
#[doc(inline)]
|
||||
pub use data_panel_message_handler::*;
|
||||
@@ -1,5 +1,8 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use super::utility_types::misc::{GroupFolderType, SnappingState};
|
||||
use crate::messages::input_mapper::utility_types::input_keyboard::Key;
|
||||
use crate::messages::portfolio::document::data_panel::DataPanelMessage;
|
||||
use crate::messages::portfolio::document::overlays::utility_types::OverlayContext;
|
||||
use crate::messages::portfolio::document::overlays::utility_types::OverlaysType;
|
||||
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
||||
@@ -31,6 +34,8 @@ pub enum DocumentMessage {
|
||||
Overlays(OverlaysMessage),
|
||||
#[child]
|
||||
PropertiesPanel(PropertiesPanelMessage),
|
||||
#[child]
|
||||
DataPanel(DataPanelMessage),
|
||||
|
||||
// Messages
|
||||
AlignSelectedLayers {
|
||||
@@ -48,7 +53,9 @@ pub enum DocumentMessage {
|
||||
DocumentHistoryBackward,
|
||||
DocumentHistoryForward,
|
||||
DocumentStructureChanged,
|
||||
DrawArtboardOverlays(OverlayContext),
|
||||
DrawArtboardOverlays {
|
||||
context: OverlayContext,
|
||||
},
|
||||
DuplicateSelectedLayers,
|
||||
EnterNestedNetwork {
|
||||
node_id: NodeId,
|
||||
@@ -67,9 +74,15 @@ pub enum DocumentMessage {
|
||||
open: bool,
|
||||
},
|
||||
GraphViewOverlayToggle,
|
||||
GridOptions(GridSnapping),
|
||||
GridOverlays(OverlayContext),
|
||||
GridVisibility(bool),
|
||||
GridOptions {
|
||||
options: GridSnapping,
|
||||
},
|
||||
GridOverlays {
|
||||
context: OverlayContext,
|
||||
},
|
||||
GridVisibility {
|
||||
visible: bool,
|
||||
},
|
||||
GroupSelectedLayers {
|
||||
group_folder_type: GroupFolderType,
|
||||
},
|
||||
@@ -105,6 +118,10 @@ pub enum DocumentMessage {
|
||||
RenderRulers,
|
||||
RenderScrollbars,
|
||||
SaveDocument,
|
||||
SaveDocumentAs,
|
||||
SavedDocument {
|
||||
path: Option<PathBuf>,
|
||||
},
|
||||
SelectParentLayer,
|
||||
SelectAllLayers,
|
||||
SelectedLayersLower,
|
||||
@@ -182,7 +199,7 @@ pub enum DocumentMessage {
|
||||
UpdateUpstreamTransforms {
|
||||
upstream_footprints: HashMap<NodeId, Footprint>,
|
||||
local_transforms: HashMap<NodeId, DAffine2>,
|
||||
first_instance_source_id: HashMap<NodeId, Option<NodeId>>,
|
||||
first_element_source_id: HashMap<NodeId, Option<NodeId>>,
|
||||
},
|
||||
UpdateClickTargets {
|
||||
click_targets: HashMap<NodeId, Vec<ClickTarget>>,
|
||||
|
||||
@@ -6,9 +6,10 @@ use super::utility_types::misc::{GroupFolderType, SNAP_FUNCTIONS_FOR_BOUNDING_BO
|
||||
use super::utility_types::network_interface::{self, NodeNetworkInterface, TransactionStatus};
|
||||
use super::utility_types::nodes::{CollapsedLayers, SelectedNodes};
|
||||
use crate::application::{GRAPHITE_GIT_COMMIT_HASH, generate_uuid};
|
||||
use crate::consts::{ASYMPTOTIC_EFFECT, COLOR_OVERLAY_GRAY, DEFAULT_DOCUMENT_NAME, FILE_SAVE_SUFFIX, SCALE_EFFECT, SCROLLBAR_SPACING, VIEWPORT_ROTATE_SNAP_INTERVAL};
|
||||
use crate::consts::{ASYMPTOTIC_EFFECT, COLOR_OVERLAY_GRAY, DEFAULT_DOCUMENT_NAME, FILE_EXTENSION, SCALE_EFFECT, SCROLLBAR_SPACING, VIEWPORT_ROTATE_SNAP_INTERVAL};
|
||||
use crate::messages::input_mapper::utility_types::macros::action_keys;
|
||||
use crate::messages::layout::utility_types::widget_prelude::*;
|
||||
use crate::messages::portfolio::document::data_panel::{DataPanelMessageContext, DataPanelMessageHandler};
|
||||
use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn;
|
||||
use crate::messages::portfolio::document::node_graph::NodeGraphMessageContext;
|
||||
use crate::messages::portfolio::document::overlays::grid_overlays::{grid_overlay, overlay_options};
|
||||
@@ -16,8 +17,9 @@ use crate::messages::portfolio::document::overlays::utility_types::{OverlaysType
|
||||
use crate::messages::portfolio::document::properties_panel::properties_panel_message_handler::PropertiesPanelMessageContext;
|
||||
use crate::messages::portfolio::document::utility_types::document_metadata::{DocumentMetadata, LayerNodeIdentifier};
|
||||
use crate::messages::portfolio::document::utility_types::misc::{AlignAggregate, AlignAxis, DocumentMode, FlipAxis, PTZ};
|
||||
use crate::messages::portfolio::document::utility_types::network_interface::{FlowType, InputConnector, NodeTemplate};
|
||||
use crate::messages::portfolio::document::utility_types::network_interface::{FlowType, InputConnector, NodeTemplate, OutputConnector};
|
||||
use crate::messages::portfolio::document::utility_types::nodes::RawBuffer;
|
||||
use crate::messages::portfolio::utility_types::PanelType;
|
||||
use crate::messages::portfolio::utility_types::PersistentData;
|
||||
use crate::messages::prelude::*;
|
||||
use crate::messages::tool::common_functionality::graph_modification_utils::{self, get_blend_mode, get_fill, get_opacity};
|
||||
@@ -25,17 +27,21 @@ use crate::messages::tool::tool_messages::select_tool::SelectToolPointerKeys;
|
||||
use crate::messages::tool::tool_messages::tool_prelude::Key;
|
||||
use crate::messages::tool::utility_types::ToolType;
|
||||
use crate::node_graph_executor::NodeGraphExecutor;
|
||||
use bezier_rs::Subpath;
|
||||
use glam::{DAffine2, DVec2, IVec2};
|
||||
use graph_craft::document::value::TaggedValue;
|
||||
use graph_craft::document::{NodeId, NodeInput, NodeNetwork, OldNodeNetwork};
|
||||
use graphene_std::math::quad::Quad;
|
||||
use graphene_std::path_bool::{boolean_intersect, path_bool_lib};
|
||||
use graphene_std::raster::BlendMode;
|
||||
use graphene_std::raster_types::{Raster, RasterDataTable};
|
||||
use graphene_std::raster_types::Raster;
|
||||
use graphene_std::subpath::Subpath;
|
||||
use graphene_std::table::Table;
|
||||
use graphene_std::vector::PointId;
|
||||
use graphene_std::vector::click_target::{ClickTarget, ClickTargetType};
|
||||
use graphene_std::vector::misc::{dvec2_to_point, point_to_dvec2};
|
||||
use graphene_std::vector::style::ViewMode;
|
||||
use kurbo::{Affine, CubicBez, Line, ParamCurve, PathSeg, QuadBez};
|
||||
use std::path::PathBuf;
|
||||
use std::time::Duration;
|
||||
|
||||
#[derive(ExtractField)]
|
||||
@@ -47,6 +53,9 @@ pub struct DocumentMessageContext<'a> {
|
||||
pub current_tool: &'a ToolType,
|
||||
pub preferences: &'a PreferencesMessageHandler,
|
||||
pub device_pixel_ratio: f64,
|
||||
pub data_panel_open: bool,
|
||||
pub layers_panel_open: bool,
|
||||
pub properties_panel_open: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize, ExtractField)]
|
||||
@@ -61,9 +70,11 @@ pub struct DocumentMessageHandler {
|
||||
#[serde(skip)]
|
||||
pub node_graph_handler: NodeGraphMessageHandler,
|
||||
#[serde(skip)]
|
||||
overlays_message_handler: OverlaysMessageHandler,
|
||||
pub overlays_message_handler: OverlaysMessageHandler,
|
||||
#[serde(skip)]
|
||||
properties_panel_message_handler: PropertiesPanelMessageHandler,
|
||||
pub properties_panel_message_handler: PropertiesPanelMessageHandler,
|
||||
#[serde(skip)]
|
||||
pub data_panel_message_handler: DataPanelMessageHandler,
|
||||
|
||||
// ============================================
|
||||
// Fields that are saved in the document format
|
||||
@@ -74,8 +85,6 @@ pub struct DocumentMessageHandler {
|
||||
/// List of the [`LayerNodeIdentifier`]s that are currently collapsed by the user in the Layers panel.
|
||||
/// Collapsed means that the expansion arrow isn't set to show the children of these layers.
|
||||
pub collapsed: CollapsedLayers,
|
||||
/// The name of the document, which is displayed in the tab and title bar of the editor.
|
||||
pub name: String,
|
||||
/// The full Git commit hash of the Graphite repository that was used to build the editor.
|
||||
/// We save this to provide a hint about which version of the editor was used to create the document.
|
||||
pub commit_hash: String,
|
||||
@@ -102,6 +111,12 @@ pub struct DocumentMessageHandler {
|
||||
// Fields omitted from the saved document format
|
||||
// =============================================
|
||||
//
|
||||
/// The name of the document, which is displayed in the tab and title bar of the editor.
|
||||
#[serde(skip)]
|
||||
pub name: String,
|
||||
/// The path of the to the document file.
|
||||
#[serde(skip)]
|
||||
pub(crate) path: Option<PathBuf>,
|
||||
/// Path to network currently viewed in the node graph overlay. This will eventually be stored in each panel, so that multiple panels can refer to different networks
|
||||
#[serde(skip)]
|
||||
breadcrumb_network_path: Vec<NodeId>,
|
||||
@@ -139,12 +154,12 @@ impl Default for DocumentMessageHandler {
|
||||
node_graph_handler: NodeGraphMessageHandler::default(),
|
||||
overlays_message_handler: OverlaysMessageHandler::default(),
|
||||
properties_panel_message_handler: PropertiesPanelMessageHandler::default(),
|
||||
data_panel_message_handler: DataPanelMessageHandler::default(),
|
||||
// ============================================
|
||||
// Fields that are saved in the document format
|
||||
// ============================================
|
||||
network_interface: default_document_network_interface(),
|
||||
collapsed: CollapsedLayers::default(),
|
||||
name: DEFAULT_DOCUMENT_NAME.to_string(),
|
||||
commit_hash: GRAPHITE_GIT_COMMIT_HASH.to_string(),
|
||||
document_ptz: PTZ::default(),
|
||||
document_mode: DocumentMode::DesignMode,
|
||||
@@ -157,6 +172,8 @@ impl Default for DocumentMessageHandler {
|
||||
// =============================================
|
||||
// Fields omitted from the saved document format
|
||||
// =============================================
|
||||
name: DEFAULT_DOCUMENT_NAME.to_string(),
|
||||
path: None,
|
||||
breadcrumb_network_path: Vec::new(),
|
||||
selection_network_path: Vec::new(),
|
||||
document_undo_history: VecDeque::new(),
|
||||
@@ -180,6 +197,9 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
|
||||
current_tool,
|
||||
preferences,
|
||||
device_pixel_ratio,
|
||||
data_panel_open,
|
||||
layers_panel_open,
|
||||
properties_panel_open,
|
||||
} = context;
|
||||
|
||||
match message {
|
||||
@@ -217,9 +237,20 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
|
||||
document_name: self.name.as_str(),
|
||||
executor,
|
||||
persistent_data,
|
||||
properties_panel_open,
|
||||
};
|
||||
self.properties_panel_message_handler.process_message(message, responses, context);
|
||||
}
|
||||
DocumentMessage::DataPanel(message) => {
|
||||
self.data_panel_message_handler.process_message(
|
||||
message,
|
||||
responses,
|
||||
DataPanelMessageContext {
|
||||
network_interface: &mut self.network_interface,
|
||||
data_panel_open,
|
||||
},
|
||||
);
|
||||
}
|
||||
DocumentMessage::NodeGraph(message) => {
|
||||
self.node_graph_handler.process_message(
|
||||
message,
|
||||
@@ -235,6 +266,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
|
||||
graph_fade_artwork_percentage: self.graph_fade_artwork_percentage,
|
||||
navigation_handler: &self.navigation_handler,
|
||||
preferences,
|
||||
layers_panel_open,
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -350,14 +382,17 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
|
||||
DocumentMessage::DocumentHistoryBackward => self.undo_with_history(ipp, responses),
|
||||
DocumentMessage::DocumentHistoryForward => self.redo_with_history(ipp, responses),
|
||||
DocumentMessage::DocumentStructureChanged => {
|
||||
self.update_layers_panel_control_bar_widgets(responses);
|
||||
self.update_layers_panel_bottom_bar_widgets(responses);
|
||||
if layers_panel_open {
|
||||
self.network_interface.load_structure();
|
||||
let data_buffer: RawBuffer = self.serialize_root();
|
||||
|
||||
self.network_interface.load_structure();
|
||||
let data_buffer: RawBuffer = self.serialize_root();
|
||||
responses.add(FrontendMessage::UpdateDocumentLayerStructure { data_buffer });
|
||||
self.update_layers_panel_control_bar_widgets(layers_panel_open, responses);
|
||||
self.update_layers_panel_bottom_bar_widgets(layers_panel_open, responses);
|
||||
|
||||
responses.add(FrontendMessage::UpdateDocumentLayerStructure { data_buffer });
|
||||
}
|
||||
}
|
||||
DocumentMessage::DrawArtboardOverlays(overlay_context) => {
|
||||
DocumentMessage::DrawArtboardOverlays { context: overlay_context } => {
|
||||
if !overlay_context.visibility_settings.artboard_name() {
|
||||
return;
|
||||
}
|
||||
@@ -548,24 +583,25 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
|
||||
responses.add(NodeGraphMessage::UpdateHints);
|
||||
} else {
|
||||
responses.add(ToolMessage::ActivateTool { tool_type: *current_tool });
|
||||
responses.add(OverlaysMessage::Draw); // Redraw overlays when graph is closed
|
||||
}
|
||||
}
|
||||
DocumentMessage::GraphViewOverlayToggle => {
|
||||
responses.add(DocumentMessage::GraphViewOverlay { open: !self.graph_view_overlay_open });
|
||||
}
|
||||
DocumentMessage::GridOptions(grid) => {
|
||||
self.snapping_state.grid = grid;
|
||||
DocumentMessage::GridOptions { options } => {
|
||||
self.snapping_state.grid = options;
|
||||
self.snapping_state.grid_snapping = true;
|
||||
responses.add(OverlaysMessage::Draw);
|
||||
responses.add(PortfolioMessage::UpdateDocumentWidgets);
|
||||
}
|
||||
DocumentMessage::GridOverlays(mut overlay_context) => {
|
||||
DocumentMessage::GridOverlays { context: mut overlay_context } => {
|
||||
if self.snapping_state.grid_snapping {
|
||||
grid_overlay(self, &mut overlay_context)
|
||||
}
|
||||
}
|
||||
DocumentMessage::GridVisibility(enabled) => {
|
||||
self.snapping_state.grid_snapping = enabled;
|
||||
DocumentMessage::GridVisibility { visible } => {
|
||||
self.snapping_state.grid_snapping = visible;
|
||||
responses.add(OverlaysMessage::Draw);
|
||||
}
|
||||
DocumentMessage::GroupSelectedLayers { group_folder_type } => {
|
||||
@@ -691,7 +727,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
|
||||
});
|
||||
|
||||
if layer_to_move.parent(self.metadata()) != Some(parent) {
|
||||
// TODO: Fix this so it works when dragging a layer into a group parent which has a Transform node, which used to work before #2689 caused this regression by removing the empty VectorData table row.
|
||||
// TODO: Fix this so it works when dragging a layer into a group parent which has a Transform node, which used to work before #2689 caused this regression by removing the empty vector table row.
|
||||
// TODO: See #2688 for this issue.
|
||||
let layer_local_transform = self.network_interface.document_metadata().transform_to_viewport(layer_to_move);
|
||||
let undo_transform = self.network_interface.document_metadata().transform_to_viewport(parent).inverse();
|
||||
@@ -837,7 +873,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
|
||||
|
||||
responses.add(DocumentMessage::AddTransaction);
|
||||
|
||||
let layer = graph_modification_utils::new_image_layer(RasterDataTable::new(Raster::new_cpu(image)), layer_node_id, self.new_layer_parent(true), responses);
|
||||
let layer = graph_modification_utils::new_image_layer(Table::new_from_element(Raster::new_cpu(image)), layer_node_id, self.new_layer_parent(true), responses);
|
||||
|
||||
if let Some(name) = name {
|
||||
responses.add(NodeGraphMessage::SetDisplayName {
|
||||
@@ -912,7 +948,11 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
|
||||
responses.add(OverlaysMessage::Draw);
|
||||
}
|
||||
DocumentMessage::RenameDocument { new_name } => {
|
||||
self.name = new_name;
|
||||
self.name = new_name.clone();
|
||||
|
||||
self.path = None;
|
||||
self.set_save_state(false);
|
||||
|
||||
responses.add(PortfolioMessage::UpdateOpenDocumentsList);
|
||||
responses.add(NodeGraphMessage::UpdateNewNodeGraph);
|
||||
}
|
||||
@@ -985,21 +1025,41 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
|
||||
multiplier: scrollbar_multiplier.into(),
|
||||
});
|
||||
}
|
||||
DocumentMessage::SaveDocument => {
|
||||
DocumentMessage::SaveDocument | DocumentMessage::SaveDocumentAs => {
|
||||
if let DocumentMessage::SaveDocumentAs = message {
|
||||
self.path = None;
|
||||
}
|
||||
|
||||
self.set_save_state(true);
|
||||
responses.add(PortfolioMessage::AutoSaveActiveDocument);
|
||||
// Update the save status of the just saved document
|
||||
responses.add(PortfolioMessage::UpdateOpenDocumentsList);
|
||||
|
||||
let name = match self.name.ends_with(FILE_SAVE_SUFFIX) {
|
||||
true => self.name.clone(),
|
||||
false => self.name.clone() + FILE_SAVE_SUFFIX,
|
||||
};
|
||||
responses.add(FrontendMessage::TriggerDownloadTextFile {
|
||||
document: self.serialize_document(),
|
||||
name,
|
||||
responses.add(FrontendMessage::TriggerSaveDocument {
|
||||
document_id,
|
||||
name: format!("{}.{}", self.name.clone(), FILE_EXTENSION),
|
||||
path: self.path.clone(),
|
||||
content: self.serialize_document().into_bytes(),
|
||||
})
|
||||
}
|
||||
DocumentMessage::SavedDocument { path } => {
|
||||
self.path = path;
|
||||
|
||||
// Update the name to match the file stem
|
||||
let document_name_from_path = self.path.as_ref().and_then(|path| {
|
||||
if path.extension().is_some_and(|e| e == FILE_EXTENSION) {
|
||||
path.file_stem().map(|n| n.to_string_lossy().to_string())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
if let Some(name) = document_name_from_path {
|
||||
self.name = name;
|
||||
|
||||
responses.add(PortfolioMessage::UpdateOpenDocumentsList);
|
||||
responses.add(NodeGraphMessage::UpdateNewNodeGraph);
|
||||
}
|
||||
}
|
||||
DocumentMessage::SelectParentLayer => {
|
||||
let selected_nodes = self.network_interface.selected_nodes();
|
||||
let selected_layers = selected_nodes.selected_layers(self.metadata());
|
||||
@@ -1022,7 +1082,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
|
||||
if !parent_layers.is_empty() {
|
||||
let nodes = parent_layers.into_iter().collect();
|
||||
responses.add(NodeGraphMessage::SelectedNodesSet { nodes });
|
||||
responses.add(BroadcastEvent::SelectionChanged);
|
||||
responses.add(EventMessage::SelectionChanged);
|
||||
}
|
||||
}
|
||||
DocumentMessage::SelectAllLayers => {
|
||||
@@ -1097,7 +1157,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
|
||||
} else {
|
||||
responses.add_front(NodeGraphMessage::SelectedNodesAdd { nodes: vec![id] });
|
||||
}
|
||||
responses.add(BroadcastEvent::SelectionChanged);
|
||||
responses.add(EventMessage::SelectionChanged);
|
||||
} else {
|
||||
nodes.push(id);
|
||||
}
|
||||
@@ -1117,7 +1177,6 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
|
||||
}
|
||||
}
|
||||
DocumentMessage::SetActivePanel { active_panel: panel } => {
|
||||
use crate::messages::portfolio::utility_types::PanelType;
|
||||
match panel {
|
||||
PanelType::Document => {
|
||||
if self.graph_view_overlay_open {
|
||||
@@ -1167,7 +1226,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
|
||||
Some(overlays_type) => overlays_type,
|
||||
None => {
|
||||
visibility_settings.all = visible;
|
||||
responses.add(BroadcastEvent::ToolAbort);
|
||||
responses.add(EventMessage::ToolAbort);
|
||||
responses.add(OverlaysMessage::Draw);
|
||||
return;
|
||||
}
|
||||
@@ -1190,7 +1249,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
|
||||
OverlaysType::Handles => visibility_settings.handles = visible,
|
||||
}
|
||||
|
||||
responses.add(BroadcastEvent::ToolAbort);
|
||||
responses.add(EventMessage::ToolAbort);
|
||||
responses.add(OverlaysMessage::Draw);
|
||||
}
|
||||
DocumentMessage::SetRangeSelectionLayer { new_layer } => {
|
||||
@@ -1302,10 +1361,10 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
|
||||
DocumentMessage::UpdateUpstreamTransforms {
|
||||
upstream_footprints,
|
||||
local_transforms,
|
||||
first_instance_source_id,
|
||||
first_element_source_id,
|
||||
} => {
|
||||
self.network_interface.update_transforms(upstream_footprints, local_transforms);
|
||||
self.network_interface.update_first_instance_source_id(first_instance_source_id);
|
||||
self.network_interface.update_first_element_source_id(first_element_source_id);
|
||||
}
|
||||
DocumentMessage::UpdateClickTargets { click_targets } => {
|
||||
// TODO: Allow non layer nodes to have click targets
|
||||
@@ -1404,12 +1463,14 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
|
||||
let transform = self.navigation_handler.calculate_offset_transform(ipp.viewport_bounds.center(), &self.document_ptz);
|
||||
self.network_interface.set_document_to_viewport_transform(transform);
|
||||
// Ensure selection box is kept in sync with the pointer when the PTZ changes
|
||||
responses.add(SelectToolMessage::PointerMove(SelectToolPointerKeys {
|
||||
axis_align: Key::Shift,
|
||||
snap_angle: Key::Shift,
|
||||
center: Key::Alt,
|
||||
duplicate: Key::Alt,
|
||||
}));
|
||||
responses.add(SelectToolMessage::PointerMove {
|
||||
modifier_keys: SelectToolPointerKeys {
|
||||
axis_align: Key::Shift,
|
||||
snap_angle: Key::Shift,
|
||||
center: Key::Alt,
|
||||
duplicate: Key::Alt,
|
||||
},
|
||||
});
|
||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||
} else {
|
||||
let Some(network_metadata) = self.network_interface.network_metadata(&self.breadcrumb_network_path) else {
|
||||
@@ -1438,11 +1499,11 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
|
||||
}
|
||||
DocumentMessage::SelectionStepBack => {
|
||||
self.network_interface.selection_step_back(&self.selection_network_path);
|
||||
responses.add(BroadcastEvent::SelectionChanged);
|
||||
responses.add(EventMessage::SelectionChanged);
|
||||
}
|
||||
DocumentMessage::SelectionStepForward => {
|
||||
self.network_interface.selection_step_forward(&self.selection_network_path);
|
||||
responses.add(BroadcastEvent::SelectionChanged);
|
||||
responses.add(EventMessage::SelectionChanged);
|
||||
}
|
||||
DocumentMessage::WrapContentInArtboard { place_artboard_at_origin } => {
|
||||
// Get bounding box of all layers
|
||||
@@ -1530,6 +1591,10 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
|
||||
ZoomCanvasToFitAll,
|
||||
);
|
||||
|
||||
// Additional actions available on desktop
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
common.extend(actions!(DocumentMessageDiscriminant::SaveDocumentAs));
|
||||
|
||||
// Additional actions if there are any selected layers
|
||||
if self.network_interface.selected_nodes().selected_layers(self.metadata()).next().is_some() {
|
||||
let mut select = actions!(DocumentMessageDiscriminant;
|
||||
@@ -1717,7 +1782,8 @@ impl DocumentMessageHandler {
|
||||
|
||||
pub fn deserialize_document(serialized_content: &str) -> Result<Self, EditorError> {
|
||||
let document_message_handler = serde_json::from_str::<DocumentMessageHandler>(serialized_content)
|
||||
.or_else(|_| {
|
||||
.or_else(|e| {
|
||||
log::warn!("failed to directly load document with the following error: {e}. Trying old DocumentMessageHandler");
|
||||
// TODO: Eventually remove this document upgrade code
|
||||
#[derive(Debug, serde::Serialize, serde::Deserialize)]
|
||||
pub struct OldDocumentMessageHandler {
|
||||
@@ -1870,10 +1936,11 @@ impl DocumentMessageHandler {
|
||||
responses.add(PortfolioMessage::UpdateOpenDocumentsList);
|
||||
responses.add(NodeGraphMessage::SelectedNodesUpdated);
|
||||
responses.add(NodeGraphMessage::ForceRunDocumentGraph);
|
||||
|
||||
// TODO: Remove once the footprint is used to load the imports/export distances from the edge
|
||||
responses.add(NodeGraphMessage::UnloadWires);
|
||||
responses.add(NodeGraphMessage::SetGridAlignedEdges);
|
||||
responses.add(Message::StartBuffer);
|
||||
|
||||
Some(previous_network)
|
||||
}
|
||||
pub fn redo_with_history(&mut self, ipp: &InputPreprocessorMessageHandler, responses: &mut VecDeque<Message>) {
|
||||
@@ -2145,7 +2212,7 @@ impl DocumentMessageHandler {
|
||||
},
|
||||
LayoutGroup::Row {
|
||||
widgets: {
|
||||
let mut checkbox_id = CheckboxId::default();
|
||||
let checkbox_id = CheckboxId::new();
|
||||
vec![
|
||||
CheckboxInput::new(self.overlays_visibility_settings.artboard_name)
|
||||
.on_update(|optional_input: &CheckboxInput| {
|
||||
@@ -2155,15 +2222,15 @@ impl DocumentMessageHandler {
|
||||
}
|
||||
.into()
|
||||
})
|
||||
.for_label(checkbox_id.clone())
|
||||
.for_label(checkbox_id)
|
||||
.widget_holder(),
|
||||
TextLabel::new("Artboard Name".to_string()).for_checkbox(&mut checkbox_id).widget_holder(),
|
||||
TextLabel::new("Artboard Name".to_string()).for_checkbox(checkbox_id).widget_holder(),
|
||||
]
|
||||
},
|
||||
},
|
||||
LayoutGroup::Row {
|
||||
widgets: {
|
||||
let mut checkbox_id = CheckboxId::default();
|
||||
let checkbox_id = CheckboxId::new();
|
||||
vec![
|
||||
CheckboxInput::new(self.overlays_visibility_settings.transform_measurement)
|
||||
.on_update(|optional_input: &CheckboxInput| {
|
||||
@@ -2173,9 +2240,9 @@ impl DocumentMessageHandler {
|
||||
}
|
||||
.into()
|
||||
})
|
||||
.for_label(checkbox_id.clone())
|
||||
.for_label(checkbox_id)
|
||||
.widget_holder(),
|
||||
TextLabel::new("G/R/S Measurement".to_string()).for_checkbox(&mut checkbox_id).widget_holder(),
|
||||
TextLabel::new("G/R/S Measurement".to_string()).for_checkbox(checkbox_id).widget_holder(),
|
||||
]
|
||||
},
|
||||
},
|
||||
@@ -2184,7 +2251,7 @@ impl DocumentMessageHandler {
|
||||
},
|
||||
LayoutGroup::Row {
|
||||
widgets: {
|
||||
let mut checkbox_id = CheckboxId::default();
|
||||
let checkbox_id = CheckboxId::new();
|
||||
vec![
|
||||
CheckboxInput::new(self.overlays_visibility_settings.quick_measurement)
|
||||
.on_update(|optional_input: &CheckboxInput| {
|
||||
@@ -2194,15 +2261,15 @@ impl DocumentMessageHandler {
|
||||
}
|
||||
.into()
|
||||
})
|
||||
.for_label(checkbox_id.clone())
|
||||
.for_label(checkbox_id)
|
||||
.widget_holder(),
|
||||
TextLabel::new("Quick Measurement".to_string()).for_checkbox(&mut checkbox_id).widget_holder(),
|
||||
TextLabel::new("Quick Measurement".to_string()).for_checkbox(checkbox_id).widget_holder(),
|
||||
]
|
||||
},
|
||||
},
|
||||
LayoutGroup::Row {
|
||||
widgets: {
|
||||
let mut checkbox_id = CheckboxId::default();
|
||||
let checkbox_id = CheckboxId::new();
|
||||
vec![
|
||||
CheckboxInput::new(self.overlays_visibility_settings.transform_cage)
|
||||
.on_update(|optional_input: &CheckboxInput| {
|
||||
@@ -2212,15 +2279,15 @@ impl DocumentMessageHandler {
|
||||
}
|
||||
.into()
|
||||
})
|
||||
.for_label(checkbox_id.clone())
|
||||
.for_label(checkbox_id)
|
||||
.widget_holder(),
|
||||
TextLabel::new("Transform Cage".to_string()).for_checkbox(&mut checkbox_id).widget_holder(),
|
||||
TextLabel::new("Transform Cage".to_string()).for_checkbox(checkbox_id).widget_holder(),
|
||||
]
|
||||
},
|
||||
},
|
||||
LayoutGroup::Row {
|
||||
widgets: {
|
||||
let mut checkbox_id = CheckboxId::default();
|
||||
let checkbox_id = CheckboxId::new();
|
||||
vec![
|
||||
CheckboxInput::new(self.overlays_visibility_settings.compass_rose)
|
||||
.on_update(|optional_input: &CheckboxInput| {
|
||||
@@ -2230,15 +2297,15 @@ impl DocumentMessageHandler {
|
||||
}
|
||||
.into()
|
||||
})
|
||||
.for_label(checkbox_id.clone())
|
||||
.for_label(checkbox_id)
|
||||
.widget_holder(),
|
||||
TextLabel::new("Transform Dial".to_string()).for_checkbox(&mut checkbox_id).widget_holder(),
|
||||
TextLabel::new("Transform Dial".to_string()).for_checkbox(checkbox_id).widget_holder(),
|
||||
]
|
||||
},
|
||||
},
|
||||
LayoutGroup::Row {
|
||||
widgets: {
|
||||
let mut checkbox_id = CheckboxId::default();
|
||||
let checkbox_id = CheckboxId::new();
|
||||
vec![
|
||||
CheckboxInput::new(self.overlays_visibility_settings.pivot)
|
||||
.on_update(|optional_input: &CheckboxInput| {
|
||||
@@ -2248,15 +2315,15 @@ impl DocumentMessageHandler {
|
||||
}
|
||||
.into()
|
||||
})
|
||||
.for_label(checkbox_id.clone())
|
||||
.for_label(checkbox_id)
|
||||
.widget_holder(),
|
||||
TextLabel::new("Transform Pivot".to_string()).for_checkbox(&mut checkbox_id).widget_holder(),
|
||||
TextLabel::new("Transform Pivot".to_string()).for_checkbox(checkbox_id).widget_holder(),
|
||||
]
|
||||
},
|
||||
},
|
||||
LayoutGroup::Row {
|
||||
widgets: {
|
||||
let mut checkbox_id = CheckboxId::default();
|
||||
let checkbox_id = CheckboxId::new();
|
||||
vec![
|
||||
CheckboxInput::new(self.overlays_visibility_settings.pivot)
|
||||
.on_update(|optional_input: &CheckboxInput| {
|
||||
@@ -2266,15 +2333,15 @@ impl DocumentMessageHandler {
|
||||
}
|
||||
.into()
|
||||
})
|
||||
.for_label(checkbox_id.clone())
|
||||
.for_label(checkbox_id)
|
||||
.widget_holder(),
|
||||
TextLabel::new("Transform Origin".to_string()).for_checkbox(&mut checkbox_id).widget_holder(),
|
||||
TextLabel::new("Transform Origin".to_string()).for_checkbox(checkbox_id).widget_holder(),
|
||||
]
|
||||
},
|
||||
},
|
||||
LayoutGroup::Row {
|
||||
widgets: {
|
||||
let mut checkbox_id = CheckboxId::default();
|
||||
let checkbox_id = CheckboxId::new();
|
||||
vec![
|
||||
CheckboxInput::new(self.overlays_visibility_settings.hover_outline)
|
||||
.on_update(|optional_input: &CheckboxInput| {
|
||||
@@ -2284,15 +2351,15 @@ impl DocumentMessageHandler {
|
||||
}
|
||||
.into()
|
||||
})
|
||||
.for_label(checkbox_id.clone())
|
||||
.for_label(checkbox_id)
|
||||
.widget_holder(),
|
||||
TextLabel::new("Hover Outline".to_string()).for_checkbox(&mut checkbox_id).widget_holder(),
|
||||
TextLabel::new("Hover Outline".to_string()).for_checkbox(checkbox_id).widget_holder(),
|
||||
]
|
||||
},
|
||||
},
|
||||
LayoutGroup::Row {
|
||||
widgets: {
|
||||
let mut checkbox_id = CheckboxId::default();
|
||||
let checkbox_id = CheckboxId::new();
|
||||
vec![
|
||||
CheckboxInput::new(self.overlays_visibility_settings.selection_outline)
|
||||
.on_update(|optional_input: &CheckboxInput| {
|
||||
@@ -2302,9 +2369,9 @@ impl DocumentMessageHandler {
|
||||
}
|
||||
.into()
|
||||
})
|
||||
.for_label(checkbox_id.clone())
|
||||
.for_label(checkbox_id)
|
||||
.widget_holder(),
|
||||
TextLabel::new("Selection Outline".to_string()).for_checkbox(&mut checkbox_id).widget_holder(),
|
||||
TextLabel::new("Selection Outline".to_string()).for_checkbox(checkbox_id).widget_holder(),
|
||||
]
|
||||
},
|
||||
},
|
||||
@@ -2313,7 +2380,7 @@ impl DocumentMessageHandler {
|
||||
},
|
||||
LayoutGroup::Row {
|
||||
widgets: {
|
||||
let mut checkbox_id = CheckboxId::default();
|
||||
let checkbox_id = CheckboxId::new();
|
||||
vec![
|
||||
CheckboxInput::new(self.overlays_visibility_settings.path)
|
||||
.on_update(|optional_input: &CheckboxInput| {
|
||||
@@ -2323,15 +2390,15 @@ impl DocumentMessageHandler {
|
||||
}
|
||||
.into()
|
||||
})
|
||||
.for_label(checkbox_id.clone())
|
||||
.for_label(checkbox_id)
|
||||
.widget_holder(),
|
||||
TextLabel::new("Path".to_string()).for_checkbox(&mut checkbox_id).widget_holder(),
|
||||
TextLabel::new("Path".to_string()).for_checkbox(checkbox_id).widget_holder(),
|
||||
]
|
||||
},
|
||||
},
|
||||
LayoutGroup::Row {
|
||||
widgets: {
|
||||
let mut checkbox_id = CheckboxId::default();
|
||||
let checkbox_id = CheckboxId::new();
|
||||
vec![
|
||||
CheckboxInput::new(self.overlays_visibility_settings.anchors)
|
||||
.on_update(|optional_input: &CheckboxInput| {
|
||||
@@ -2341,15 +2408,15 @@ impl DocumentMessageHandler {
|
||||
}
|
||||
.into()
|
||||
})
|
||||
.for_label(checkbox_id.clone())
|
||||
.for_label(checkbox_id)
|
||||
.widget_holder(),
|
||||
TextLabel::new("Anchors".to_string()).for_checkbox(&mut checkbox_id).widget_holder(),
|
||||
TextLabel::new("Anchors".to_string()).for_checkbox(checkbox_id).widget_holder(),
|
||||
]
|
||||
},
|
||||
},
|
||||
LayoutGroup::Row {
|
||||
widgets: {
|
||||
let mut checkbox_id = CheckboxId::default();
|
||||
let checkbox_id = CheckboxId::new();
|
||||
vec![
|
||||
CheckboxInput::new(self.overlays_visibility_settings.handles)
|
||||
.disabled(!self.overlays_visibility_settings.anchors)
|
||||
@@ -2360,11 +2427,11 @@ impl DocumentMessageHandler {
|
||||
}
|
||||
.into()
|
||||
})
|
||||
.for_label(checkbox_id.clone())
|
||||
.for_label(checkbox_id)
|
||||
.widget_holder(),
|
||||
TextLabel::new("Handles".to_string())
|
||||
.disabled(!self.overlays_visibility_settings.anchors)
|
||||
.for_checkbox(&mut checkbox_id)
|
||||
.for_checkbox(checkbox_id)
|
||||
.widget_holder(),
|
||||
]
|
||||
},
|
||||
@@ -2397,7 +2464,7 @@ impl DocumentMessageHandler {
|
||||
.into_iter()
|
||||
.chain(SNAP_FUNCTIONS_FOR_BOUNDING_BOXES.into_iter().map(|(name, closure, tooltip)| LayoutGroup::Row {
|
||||
widgets: {
|
||||
let mut checkbox_id = CheckboxId::default();
|
||||
let checkbox_id = CheckboxId::new();
|
||||
vec![
|
||||
CheckboxInput::new(*closure(&mut snapping_state))
|
||||
.on_update(move |input: &CheckboxInput| {
|
||||
@@ -2408,9 +2475,9 @@ impl DocumentMessageHandler {
|
||||
.into()
|
||||
})
|
||||
.tooltip(tooltip)
|
||||
.for_label(checkbox_id.clone())
|
||||
.for_label(checkbox_id)
|
||||
.widget_holder(),
|
||||
TextLabel::new(name).tooltip(tooltip).for_checkbox(&mut checkbox_id).widget_holder(),
|
||||
TextLabel::new(name).tooltip(tooltip).for_checkbox(checkbox_id).widget_holder(),
|
||||
]
|
||||
},
|
||||
}))
|
||||
@@ -2419,7 +2486,7 @@ impl DocumentMessageHandler {
|
||||
}])
|
||||
.chain(SNAP_FUNCTIONS_FOR_PATHS.into_iter().map(|(name, closure, tooltip)| LayoutGroup::Row {
|
||||
widgets: {
|
||||
let mut checkbox_id = CheckboxId::default();
|
||||
let checkbox_id = CheckboxId::new();
|
||||
vec![
|
||||
CheckboxInput::new(*closure(&mut snapping_state2))
|
||||
.on_update(move |input: &CheckboxInput| {
|
||||
@@ -2430,9 +2497,9 @@ impl DocumentMessageHandler {
|
||||
.into()
|
||||
})
|
||||
.tooltip(tooltip)
|
||||
.for_label(checkbox_id.clone())
|
||||
.for_label(checkbox_id)
|
||||
.widget_holder(),
|
||||
TextLabel::new(name).tooltip(tooltip).for_checkbox(&mut checkbox_id).widget_holder(),
|
||||
TextLabel::new(name).tooltip(tooltip).for_checkbox(checkbox_id).widget_holder(),
|
||||
]
|
||||
},
|
||||
}))
|
||||
@@ -2444,7 +2511,7 @@ impl DocumentMessageHandler {
|
||||
.icon("Grid")
|
||||
.tooltip("Grid")
|
||||
.tooltip_shortcut(action_keys!(DocumentMessageDiscriminant::ToggleGridVisibility))
|
||||
.on_update(|optional_input: &CheckboxInput| DocumentMessage::GridVisibility(optional_input.checked).into())
|
||||
.on_update(|optional_input: &CheckboxInput| DocumentMessage::GridVisibility { visible: optional_input.checked }.into())
|
||||
.widget_holder(),
|
||||
PopoverButton::new()
|
||||
.popover_layout(overlay_options(&self.snapping_state.grid))
|
||||
@@ -2537,7 +2604,11 @@ impl DocumentMessageHandler {
|
||||
responses.add(NodeGraphMessage::ForceRunDocumentGraph);
|
||||
}
|
||||
|
||||
pub fn update_layers_panel_control_bar_widgets(&self, responses: &mut VecDeque<Message>) {
|
||||
pub fn update_layers_panel_control_bar_widgets(&self, layers_panel_open: bool, responses: &mut VecDeque<Message>) {
|
||||
if !layers_panel_open {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get an iterator over the selected layers (excluding artboards which don't have an opacity or blend mode).
|
||||
let selected_nodes = self.network_interface.selected_nodes();
|
||||
let selected_layers_except_artboards = selected_nodes.selected_layers_except_artboards(&self.network_interface);
|
||||
@@ -2695,12 +2766,17 @@ impl DocumentMessageHandler {
|
||||
});
|
||||
}
|
||||
|
||||
pub fn update_layers_panel_bottom_bar_widgets(&self, responses: &mut VecDeque<Message>) {
|
||||
pub fn update_layers_panel_bottom_bar_widgets(&mut self, layers_panel_open: bool, responses: &mut VecDeque<Message>) {
|
||||
if !layers_panel_open {
|
||||
return;
|
||||
}
|
||||
|
||||
let selected_nodes = self.network_interface.selected_nodes();
|
||||
let mut selected_layers = selected_nodes.selected_layers(self.metadata());
|
||||
let selected_layer = selected_layers.next();
|
||||
let has_selection = selected_layer.is_some();
|
||||
let has_multiple_selection = selected_layers.next().is_some();
|
||||
for _ in selected_layers {}
|
||||
|
||||
let widgets = vec![
|
||||
PopoverButton::new()
|
||||
@@ -2714,7 +2790,7 @@ impl DocumentMessageHandler {
|
||||
let graph_layer = graph_modification_utils::NodeGraphLayer::new(layer, &self.network_interface);
|
||||
let node_type = graph_layer.horizontal_layer_flow().nth(1);
|
||||
if let Some(node_id) = node_type {
|
||||
let (output_type, _) = self.network_interface.output_type(&node_id, 0, &self.selection_network_path);
|
||||
let (output_type, _) = self.network_interface.output_type(&OutputConnector::node(node_id, 0), &self.selection_network_path);
|
||||
Some(format!("type:{}", output_type.nested_type()))
|
||||
} else {
|
||||
None
|
||||
@@ -2905,7 +2981,7 @@ impl DocumentMessageHandler {
|
||||
/// Create a network interface with a single export
|
||||
fn default_document_network_interface() -> NodeNetworkInterface {
|
||||
let mut network_interface = NodeNetworkInterface::default();
|
||||
network_interface.add_export(TaggedValue::ArtboardGroup(graphene_std::ArtboardGroupTable::default()), -1, "", &[]);
|
||||
network_interface.add_export(TaggedValue::Artboard(Default::default()), -1, "", &[]);
|
||||
network_interface
|
||||
}
|
||||
|
||||
@@ -2937,10 +3013,10 @@ fn quad_to_path_lib_segments(quad: Quad) -> Vec<path_bool_lib::PathSegment> {
|
||||
}
|
||||
|
||||
fn click_targets_to_path_lib_segments<'a>(click_targets: impl Iterator<Item = &'a ClickTarget>, transform: DAffine2) -> Vec<path_bool_lib::PathSegment> {
|
||||
let segment = |bezier: bezier_rs::Bezier| match bezier.handles {
|
||||
bezier_rs::BezierHandles::Linear => path_bool_lib::PathSegment::Line(bezier.start, bezier.end),
|
||||
bezier_rs::BezierHandles::Quadratic { handle } => path_bool_lib::PathSegment::Quadratic(bezier.start, handle, bezier.end),
|
||||
bezier_rs::BezierHandles::Cubic { handle_start, handle_end } => path_bool_lib::PathSegment::Cubic(bezier.start, handle_start, handle_end, bezier.end),
|
||||
let segment = |bezier: PathSeg| match bezier {
|
||||
PathSeg::Line(line) => path_bool_lib::PathSegment::Line(point_to_dvec2(line.p0), point_to_dvec2(line.p1)),
|
||||
PathSeg::Quad(quad_bez) => path_bool_lib::PathSegment::Quadratic(point_to_dvec2(quad_bez.p0), point_to_dvec2(quad_bez.p1), point_to_dvec2(quad_bez.p2)),
|
||||
PathSeg::Cubic(cubic_bez) => path_bool_lib::PathSegment::Cubic(point_to_dvec2(cubic_bez.p0), point_to_dvec2(cubic_bez.p1), point_to_dvec2(cubic_bez.p2), point_to_dvec2(cubic_bez.p3)),
|
||||
};
|
||||
click_targets
|
||||
.filter_map(|target| {
|
||||
@@ -2951,7 +3027,7 @@ fn click_targets_to_path_lib_segments<'a>(click_targets: impl Iterator<Item = &'
|
||||
}
|
||||
})
|
||||
.flatten()
|
||||
.map(|bezier| segment(bezier.apply_transformation(|x| transform.transform_point2(x))))
|
||||
.map(|bezier| segment(Affine::new(transform.to_cols_array()) * bezier))
|
||||
.collect()
|
||||
}
|
||||
|
||||
@@ -2974,11 +3050,11 @@ impl<'a> ClickXRayIter<'a> {
|
||||
|
||||
/// Handles the checking of the layer where the target is a rect or path
|
||||
fn check_layer_area_target(&mut self, click_targets: Option<&Vec<ClickTarget>>, clip: bool, layer: LayerNodeIdentifier, path: Vec<path_bool_lib::PathSegment>, transform: DAffine2) -> XRayResult {
|
||||
// Convert back to Bezier-rs types for intersections
|
||||
// Convert back to Kurbo types for intersections
|
||||
let segment = |bezier: &path_bool_lib::PathSegment| match *bezier {
|
||||
path_bool_lib::PathSegment::Line(start, end) => bezier_rs::Bezier::from_linear_dvec2(start, end),
|
||||
path_bool_lib::PathSegment::Cubic(start, h1, h2, end) => bezier_rs::Bezier::from_cubic_dvec2(start, h1, h2, end),
|
||||
path_bool_lib::PathSegment::Quadratic(start, h1, end) => bezier_rs::Bezier::from_quadratic_dvec2(start, h1, end),
|
||||
path_bool_lib::PathSegment::Line(start, end) => PathSeg::Line(Line::new(dvec2_to_point(start), dvec2_to_point(end))),
|
||||
path_bool_lib::PathSegment::Cubic(start, h1, h2, end) => PathSeg::Cubic(CubicBez::new(dvec2_to_point(start), dvec2_to_point(h1), dvec2_to_point(h2), dvec2_to_point(end))),
|
||||
path_bool_lib::PathSegment::Quadratic(start, h1, end) => PathSeg::Quad(QuadBez::new(dvec2_to_point(start), dvec2_to_point(h1), dvec2_to_point(end))),
|
||||
path_bool_lib::PathSegment::Arc(_, _, _, _, _, _, _) => unimplemented!(),
|
||||
};
|
||||
let get_clip = || path.iter().map(segment);
|
||||
@@ -3027,7 +3103,10 @@ impl<'a> ClickXRayIter<'a> {
|
||||
XRayTarget::Quad(quad) => self.check_layer_area_target(click_targets, clip, layer, quad_to_path_lib_segments(*quad), transform),
|
||||
XRayTarget::Path(path) => self.check_layer_area_target(click_targets, clip, layer, path.clone(), transform),
|
||||
XRayTarget::Polygon(polygon) => {
|
||||
let polygon = polygon.iter_closed().map(|line| path_bool_lib::PathSegment::Line(line.start, line.end)).collect();
|
||||
let polygon = polygon
|
||||
.iter_closed()
|
||||
.map(|line| path_bool_lib::PathSegment::Line(point_to_dvec2(line.start()), point_to_dvec2(line.end())))
|
||||
.collect();
|
||||
self.check_layer_area_target(click_targets, clip, layer, polygon, transform)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,13 +2,14 @@ use super::utility_types::TransformIn;
|
||||
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
||||
use crate::messages::portfolio::document::utility_types::network_interface::NodeTemplate;
|
||||
use crate::messages::prelude::*;
|
||||
use bezier_rs::Subpath;
|
||||
use glam::{DAffine2, IVec2};
|
||||
use graph_craft::document::NodeId;
|
||||
use graphene_std::Artboard;
|
||||
use graphene_std::brush::brush_stroke::BrushStroke;
|
||||
use graphene_std::raster::BlendMode;
|
||||
use graphene_std::raster_types::{CPU, RasterDataTable};
|
||||
use graphene_std::raster_types::{CPU, Raster};
|
||||
use graphene_std::subpath::Subpath;
|
||||
use graphene_std::table::Table;
|
||||
use graphene_std::text::{Font, TypesettingConfig};
|
||||
use graphene_std::vector::PointId;
|
||||
use graphene_std::vector::VectorModificationType;
|
||||
@@ -69,7 +70,7 @@ pub enum GraphOperationMessage {
|
||||
},
|
||||
NewBitmapLayer {
|
||||
id: NodeId,
|
||||
image_frame: RasterDataTable<CPU>,
|
||||
image_frame: Table<Raster<CPU>>,
|
||||
parent: LayerNodeIdentifier,
|
||||
insert_index: usize,
|
||||
},
|
||||
|
||||
+2
-4
@@ -174,7 +174,7 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageContext<'_>> for
|
||||
GraphOperationMessage::NewVectorLayer { id, subpaths, parent, insert_index } => {
|
||||
let mut modify_inputs = ModifyInputsContext::new(network_interface, responses);
|
||||
let layer = modify_inputs.create_layer(id);
|
||||
modify_inputs.insert_vector_data(subpaths, layer, true, true, true);
|
||||
modify_inputs.insert_vector(subpaths, layer, true, true, true);
|
||||
network_interface.move_layer_to_stack(layer, parent, insert_index, &[]);
|
||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||
}
|
||||
@@ -349,7 +349,7 @@ fn import_usvg_node(modify_inputs: &mut ModifyInputsContext, node: &usvg::Node,
|
||||
let subpaths = convert_usvg_path(path);
|
||||
let bounds = subpaths.iter().filter_map(|subpath| subpath.bounding_box()).reduce(Quad::combine_bounds).unwrap_or_default();
|
||||
|
||||
modify_inputs.insert_vector_data(subpaths, layer, true, path.fill().is_some(), path.stroke().is_some());
|
||||
modify_inputs.insert_vector(subpaths, layer, true, path.fill().is_some(), path.stroke().is_some());
|
||||
|
||||
if let Some(transform_node_id) = modify_inputs.existing_node_id("Transform", true) {
|
||||
transform_utils::update_transform(modify_inputs.network_interface, &transform_node_id, transform * usvg_transform(node.abs_transform()));
|
||||
@@ -426,7 +426,6 @@ fn apply_usvg_fill(fill: &usvg::Fill, modify_inputs: &mut ModifyInputsContext, t
|
||||
Fill::Gradient(Gradient {
|
||||
start,
|
||||
end,
|
||||
transform: DAffine2::IDENTITY,
|
||||
gradient_type: GradientType::Linear,
|
||||
stops,
|
||||
})
|
||||
@@ -453,7 +452,6 @@ fn apply_usvg_fill(fill: &usvg::Fill, modify_inputs: &mut ModifyInputsContext, t
|
||||
Fill::Gradient(Gradient {
|
||||
start,
|
||||
end,
|
||||
transform: DAffine2::IDENTITY,
|
||||
gradient_type: GradientType::Radial,
|
||||
stops,
|
||||
})
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use crate::messages::portfolio::document::utility_types::network_interface::{InputConnector, NodeNetworkInterface};
|
||||
use bezier_rs::Subpath;
|
||||
use glam::{DAffine2, DVec2};
|
||||
use graph_craft::document::value::TaggedValue;
|
||||
use graph_craft::document::{NodeId, NodeInput};
|
||||
use graphene_std::subpath::Subpath;
|
||||
use graphene_std::vector::PointId;
|
||||
|
||||
/// Convert an affine transform into the tuple `(scale, angle, translation, shear)` assuming `shear.y = 0`.
|
||||
@@ -91,6 +91,32 @@ pub fn get_current_normalized_pivot(inputs: &[NodeInput]) -> DVec2 {
|
||||
if let Some(&TaggedValue::DVec2(pivot)) = inputs[5].as_value() { pivot } else { DVec2::splat(0.5) }
|
||||
}
|
||||
|
||||
/// Expand a bounds to avoid div zero errors
|
||||
fn clamp_bounds(bounds_min: DVec2, mut bounds_max: DVec2) -> [DVec2; 2] {
|
||||
let bounds_size = bounds_max - bounds_min;
|
||||
if bounds_size.x < 1e-10 {
|
||||
bounds_max.x = bounds_min.x + 1.;
|
||||
}
|
||||
if bounds_size.y < 1e-10 {
|
||||
bounds_max.y = bounds_min.y + 1.;
|
||||
}
|
||||
[bounds_min, bounds_max]
|
||||
}
|
||||
/// Returns corners of all subpaths
|
||||
fn subpath_bounds(subpaths: &[Subpath<PointId>]) -> [DVec2; 2] {
|
||||
subpaths
|
||||
.iter()
|
||||
.filter_map(|subpath| subpath.bounding_box())
|
||||
.reduce(|b1, b2| [b1[0].min(b2[0]), b1[1].max(b2[1])])
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Returns corners of all subpaths (but expanded to avoid division-by-zero errors)
|
||||
pub fn nonzero_subpath_bounds(subpaths: &[Subpath<PointId>]) -> [DVec2; 2] {
|
||||
let [bounds_min, bounds_max] = subpath_bounds(subpaths);
|
||||
clamp_bounds(bounds_min, bounds_max)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -138,29 +164,3 @@ mod tests {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Expand a bounds to avoid div zero errors
|
||||
fn clamp_bounds(bounds_min: DVec2, mut bounds_max: DVec2) -> [DVec2; 2] {
|
||||
let bounds_size = bounds_max - bounds_min;
|
||||
if bounds_size.x < 1e-10 {
|
||||
bounds_max.x = bounds_min.x + 1.;
|
||||
}
|
||||
if bounds_size.y < 1e-10 {
|
||||
bounds_max.y = bounds_min.y + 1.;
|
||||
}
|
||||
[bounds_min, bounds_max]
|
||||
}
|
||||
/// Returns corners of all subpaths
|
||||
fn subpath_bounds(subpaths: &[Subpath<PointId>]) -> [DVec2; 2] {
|
||||
subpaths
|
||||
.iter()
|
||||
.filter_map(|subpath| subpath.bounding_box())
|
||||
.reduce(|b1, b2| [b1[0].min(b2[0]), b1[1].max(b2[1])])
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Returns corners of all subpaths (but expanded to avoid division-by-zero errors)
|
||||
pub fn nonzero_subpath_bounds(subpaths: &[Subpath<PointId>]) -> [DVec2; 2] {
|
||||
let [bounds_min, bounds_max] = subpath_bounds(subpaths);
|
||||
clamp_bounds(bounds_min, bounds_max)
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ use crate::messages::portfolio::document::node_graph::document_node_definitions:
|
||||
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
||||
use crate::messages::portfolio::document::utility_types::network_interface::{self, InputConnector, NodeNetworkInterface, OutputConnector};
|
||||
use crate::messages::prelude::*;
|
||||
use bezier_rs::Subpath;
|
||||
use glam::{DAffine2, IVec2};
|
||||
use graph_craft::concrete;
|
||||
use graph_craft::document::value::TaggedValue;
|
||||
@@ -11,12 +10,14 @@ use graph_craft::document::{NodeId, NodeInput};
|
||||
use graphene_std::Artboard;
|
||||
use graphene_std::brush::brush_stroke::BrushStroke;
|
||||
use graphene_std::raster::BlendMode;
|
||||
use graphene_std::raster_types::{CPU, RasterDataTable};
|
||||
use graphene_std::raster_types::{CPU, Raster};
|
||||
use graphene_std::subpath::Subpath;
|
||||
use graphene_std::table::Table;
|
||||
use graphene_std::text::{Font, TypesettingConfig};
|
||||
use graphene_std::vector::Vector;
|
||||
use graphene_std::vector::style::{Fill, Stroke};
|
||||
use graphene_std::vector::{PointId, VectorModificationType};
|
||||
use graphene_std::vector::{VectorData, VectorDataTable};
|
||||
use graphene_std::{GraphicGroupTable, NodeInputDecleration};
|
||||
use graphene_std::{Graphic, NodeInputDecleration};
|
||||
|
||||
#[derive(PartialEq, Clone, Copy, Debug, serde::Serialize, serde::Deserialize)]
|
||||
pub enum TransformIn {
|
||||
@@ -130,11 +131,11 @@ impl<'a> ModifyInputsContext<'a> {
|
||||
/// Creates an artboard as the primary export for the document network
|
||||
pub fn create_artboard(&mut self, new_id: NodeId, artboard: Artboard) -> LayerNodeIdentifier {
|
||||
let artboard_node_template = resolve_document_node_type("Artboard").expect("Node").node_template_input_override([
|
||||
Some(NodeInput::value(TaggedValue::ArtboardGroup(graphene_std::ArtboardGroupTable::default()), true)),
|
||||
Some(NodeInput::value(TaggedValue::GraphicGroup(graphene_std::GraphicGroupTable::default()), true)),
|
||||
Some(NodeInput::value(TaggedValue::Artboard(Default::default()), true)),
|
||||
Some(NodeInput::value(TaggedValue::Graphic(Default::default()), true)),
|
||||
Some(NodeInput::value(TaggedValue::DVec2(artboard.location.into()), false)),
|
||||
Some(NodeInput::value(TaggedValue::DVec2(artboard.dimensions.into()), false)),
|
||||
Some(NodeInput::value(TaggedValue::Color(artboard.background), false)),
|
||||
Some(NodeInput::value(TaggedValue::Color(Table::new_from_element(artboard.background)), false)),
|
||||
Some(NodeInput::value(TaggedValue::Bool(artboard.clip), false)),
|
||||
]);
|
||||
self.network_interface.insert_node(new_id, artboard_node_template, &[]);
|
||||
@@ -143,7 +144,7 @@ impl<'a> ModifyInputsContext<'a> {
|
||||
|
||||
pub fn insert_boolean_data(&mut self, operation: graphene_std::path_bool::BooleanOperation, layer: LayerNodeIdentifier) {
|
||||
let boolean = resolve_document_node_type("Boolean Operation").expect("Boolean node does not exist").node_template_input_override([
|
||||
Some(NodeInput::value(TaggedValue::GraphicGroup(graphene_std::GraphicGroupTable::default()), true)),
|
||||
Some(NodeInput::value(TaggedValue::Graphic(Default::default()), true)),
|
||||
Some(NodeInput::value(TaggedValue::BooleanOperation(operation), false)),
|
||||
]);
|
||||
|
||||
@@ -152,12 +153,12 @@ impl<'a> ModifyInputsContext<'a> {
|
||||
self.network_interface.move_node_to_chain_start(&boolean_id, layer, &[]);
|
||||
}
|
||||
|
||||
pub fn insert_vector_data(&mut self, subpaths: Vec<Subpath<PointId>>, layer: LayerNodeIdentifier, include_transform: bool, include_fill: bool, include_stroke: bool) {
|
||||
let vector_data = VectorDataTable::new(VectorData::from_subpaths(subpaths, true));
|
||||
pub fn insert_vector(&mut self, subpaths: Vec<Subpath<PointId>>, layer: LayerNodeIdentifier, include_transform: bool, include_fill: bool, include_stroke: bool) {
|
||||
let vector = Table::new_from_element(Vector::from_subpaths(subpaths, true));
|
||||
|
||||
let shape = resolve_document_node_type("Path")
|
||||
.expect("Path node does not exist")
|
||||
.node_template_input_override([Some(NodeInput::value(TaggedValue::VectorData(vector_data), false))]);
|
||||
.node_template_input_override([Some(NodeInput::value(TaggedValue::Vector(vector), false))]);
|
||||
let shape_id = NodeId::new();
|
||||
self.network_interface.insert_node(shape_id, shape, &[]);
|
||||
self.network_interface.move_node_to_chain_start(&shape_id, layer, &[]);
|
||||
@@ -198,6 +199,7 @@ impl<'a> ModifyInputsContext<'a> {
|
||||
Some(NodeInput::value(TaggedValue::OptionalF64(typesetting.max_width), false)),
|
||||
Some(NodeInput::value(TaggedValue::OptionalF64(typesetting.max_height), false)),
|
||||
Some(NodeInput::value(TaggedValue::F64(typesetting.tilt), false)),
|
||||
Some(NodeInput::value(TaggedValue::TextAlign(typesetting.align), false)),
|
||||
]);
|
||||
|
||||
let text_id = NodeId::new();
|
||||
@@ -217,11 +219,11 @@ impl<'a> ModifyInputsContext<'a> {
|
||||
self.network_interface.move_node_to_chain_start(&stroke_id, layer, &[]);
|
||||
}
|
||||
|
||||
pub fn insert_image_data(&mut self, image_frame: RasterDataTable<CPU>, layer: LayerNodeIdentifier) {
|
||||
pub fn insert_image_data(&mut self, image_frame: Table<Raster<CPU>>, layer: LayerNodeIdentifier) {
|
||||
let transform = resolve_document_node_type("Transform").expect("Transform node does not exist").default_node_template();
|
||||
let image = resolve_document_node_type("Image Value")
|
||||
.expect("ImageValue node does not exist")
|
||||
.node_template_input_override([Some(NodeInput::value(TaggedValue::None, false)), Some(NodeInput::value(TaggedValue::RasterData(image_frame), false))]);
|
||||
.node_template_input_override([Some(NodeInput::value(TaggedValue::None, false)), Some(NodeInput::value(TaggedValue::Raster(image_frame), false))]);
|
||||
|
||||
let image_id = NodeId::new();
|
||||
self.network_interface.insert_node(image_id, image, &[]);
|
||||
@@ -261,7 +263,7 @@ impl<'a> ModifyInputsContext<'a> {
|
||||
}
|
||||
|
||||
/// Gets the node id of a node with a specific reference (name) that is upstream (leftward) from the layer node, but before reaching another upstream layer stack.
|
||||
/// For example, if given a group layer, this would find a requested "Transform" or "Boolean Operation" node in its chain, between the group layer and its layer stack child contents.
|
||||
/// For example, if given a parent layer, this would find a requested "Transform" or "Boolean Operation" node in its chain, between the parent layer and its layer stack child contents.
|
||||
/// It would also travel up an entire layer that's not fed by a stack until reaching the generator node, such as a "Rectangle" or "Path" layer.
|
||||
pub fn locate_node_in_layer_chain(reference_name: &str, left_of_layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<NodeId> {
|
||||
let upstream = network_interface.upstream_flow_back_from_nodes(vec![left_of_layer.to_node()], &[], network_interface::FlowType::HorizontalFlow);
|
||||
@@ -294,14 +296,15 @@ impl<'a> ModifyInputsContext<'a> {
|
||||
pub fn create_node(&mut self, reference: &str) -> Option<NodeId> {
|
||||
let output_layer = self.get_output_layer()?;
|
||||
let Some(node_definition) = resolve_document_node_type(reference) else {
|
||||
log::error!("Node type {} does not exist in ModifyInputsContext::existing_node_id", reference);
|
||||
log::error!("Node type {reference} does not exist in ModifyInputsContext::existing_node_id");
|
||||
return None;
|
||||
};
|
||||
// If inserting a path node, insert a Flatten Path if the type is a graphic group.
|
||||
// TODO: Allow the path node to operate on Graphic Group data by utilizing the reference for each vector data in a group.
|
||||
|
||||
// If inserting a 'Path' node, insert a 'Flatten Path' node if the type is `Graphic`.
|
||||
// TODO: Allow the 'Path' node to operate on table data by utilizing the reference (index or ID?) for each row.
|
||||
if node_definition.identifier == "Path" {
|
||||
let layer_input_type = self.network_interface.input_type(&InputConnector::node(output_layer.to_node(), 1), &[]).0.nested_type().clone();
|
||||
if layer_input_type == concrete!(GraphicGroupTable) {
|
||||
if layer_input_type == concrete!(Table<Graphic>) {
|
||||
let Some(flatten_path_definition) = resolve_document_node_type("Flatten Path") else {
|
||||
log::error!("Flatten Path does not exist in ModifyInputsContext::existing_node_id");
|
||||
return None;
|
||||
@@ -326,11 +329,11 @@ impl<'a> ModifyInputsContext<'a> {
|
||||
match &fill {
|
||||
Fill::None => {
|
||||
let input_connector = InputConnector::node(fill_node_id, backup_color_index);
|
||||
self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::OptionalColor(None), false), true);
|
||||
self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::Color(Table::new()), false), true);
|
||||
}
|
||||
Fill::Solid(color) => {
|
||||
let input_connector = InputConnector::node(fill_node_id, backup_color_index);
|
||||
self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::OptionalColor(Some(*color)), false), true);
|
||||
self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::Color(Table::new_from_element(*color)), false), true);
|
||||
}
|
||||
Fill::Gradient(gradient) => {
|
||||
let input_connector = InputConnector::node(fill_node_id, backup_gradient_index);
|
||||
@@ -369,8 +372,10 @@ impl<'a> ModifyInputsContext<'a> {
|
||||
pub fn stroke_set(&mut self, stroke: Stroke) {
|
||||
let Some(stroke_node_id) = self.existing_node_id("Stroke", true) else { return };
|
||||
|
||||
let input_connector = InputConnector::node(stroke_node_id, graphene_std::vector::stroke::ColorInput::<Option<graphene_std::Color>>::INDEX);
|
||||
self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::OptionalColor(stroke.color), false), true);
|
||||
let stroke_color = if let Some(color) = stroke.color { Table::new_from_element(color) } else { Table::new() };
|
||||
|
||||
let input_connector = InputConnector::node(stroke_node_id, graphene_std::vector::stroke::ColorInput::INDEX);
|
||||
self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::Color(stroke_color), false), true);
|
||||
let input_connector = InputConnector::node(stroke_node_id, graphene_std::vector::stroke::WeightInput::INDEX);
|
||||
self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::F64(stroke.weight), false), true);
|
||||
let input_connector = InputConnector::node(stroke_node_id, graphene_std::vector::stroke::AlignInput::INDEX);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
mod document_message;
|
||||
mod document_message_handler;
|
||||
|
||||
pub mod data_panel;
|
||||
pub mod graph_operation;
|
||||
pub mod navigation;
|
||||
pub mod node_graph;
|
||||
|
||||
@@ -139,7 +139,7 @@ impl MessageHandler<NavigationMessage, NavigationMessageContext<'_>> for Navigat
|
||||
let transformed_delta = document_to_viewport.inverse().transform_vector2(delta);
|
||||
|
||||
ptz.pan += transformed_delta;
|
||||
responses.add(BroadcastEvent::CanvasTransformed);
|
||||
responses.add(EventMessage::CanvasTransformed);
|
||||
responses.add(DocumentMessage::PTZUpdate);
|
||||
}
|
||||
NavigationMessage::CanvasPanAbortPrepare { x_not_y_axis } => {
|
||||
@@ -286,7 +286,7 @@ impl MessageHandler<NavigationMessage, NavigationMessageContext<'_>> for Navigat
|
||||
ptz.flip = !ptz.flip;
|
||||
|
||||
responses.add(DocumentMessage::PTZUpdate);
|
||||
responses.add(BroadcastEvent::CanvasTransformed);
|
||||
responses.add(EventMessage::CanvasTransformed);
|
||||
responses.add(MenuBarMessage::SendLayout);
|
||||
responses.add(PortfolioMessage::UpdateDocumentWidgets);
|
||||
}
|
||||
@@ -325,7 +325,7 @@ impl MessageHandler<NavigationMessage, NavigationMessageContext<'_>> for Navigat
|
||||
self.navigation_operation = NavigationOperation::None;
|
||||
|
||||
// Send the final messages to close out the operation
|
||||
responses.add(BroadcastEvent::CanvasTransformed);
|
||||
responses.add(EventMessage::CanvasTransformed);
|
||||
responses.add(ToolMessage::UpdateCursor);
|
||||
responses.add(ToolMessage::UpdateHints);
|
||||
responses.add(NavigateToolMessage::End);
|
||||
|
||||
@@ -19,11 +19,12 @@ use graph_craft::document::*;
|
||||
use graphene_std::brush::brush_cache::BrushCache;
|
||||
use graphene_std::extract_xy::XY;
|
||||
use graphene_std::raster::{CellularDistanceFunction, CellularReturnType, Color, DomainWarpType, FractalType, NoiseType, RedGreenBlueAlpha};
|
||||
use graphene_std::raster_types::{CPU, RasterDataTable};
|
||||
use graphene_std::raster_types::{CPU, Raster};
|
||||
use graphene_std::table::Table;
|
||||
use graphene_std::text::{Font, TypesettingConfig};
|
||||
#[allow(unused_imports)]
|
||||
use graphene_std::transform::Footprint;
|
||||
use graphene_std::vector::VectorDataTable;
|
||||
use graphene_std::vector::Vector;
|
||||
use graphene_std::*;
|
||||
use std::collections::{HashMap, HashSet, VecDeque};
|
||||
|
||||
@@ -45,7 +46,7 @@ impl NodePropertiesContext<'_> {
|
||||
return None;
|
||||
};
|
||||
widget_override_lambda(*node_id, index, self)
|
||||
.map_err(|error| log::error!("Error in widget override lambda: {}", error))
|
||||
.map_err(|error| log::error!("Error in widget override lambda: {error}"))
|
||||
.ok()
|
||||
} else {
|
||||
None
|
||||
@@ -85,7 +86,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
let custom = vec![
|
||||
// TODO: Auto-generate this from its proto node macro
|
||||
DocumentNodeDefinition {
|
||||
identifier: "Identity",
|
||||
identifier: "Passthrough",
|
||||
category: "General",
|
||||
node_template: NodeTemplate {
|
||||
document_node: DocumentNode {
|
||||
@@ -94,13 +95,13 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
..Default::default()
|
||||
},
|
||||
persistent_node_metadata: DocumentNodePersistentMetadata {
|
||||
input_metadata: vec![("In", "TODO").into()],
|
||||
input_metadata: vec![("Content", "TODO").into()],
|
||||
output_names: vec!["Out".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
},
|
||||
description: Cow::Borrowed("Passes-through the input value without changing it. This is useful for rerouting wires for organization purposes."),
|
||||
properties: Some("identity_properties"),
|
||||
description: Cow::Borrowed("Returns the input value without changing it. This is useful for rerouting wires for organization purposes."),
|
||||
properties: None,
|
||||
},
|
||||
// TODO: Auto-generate this from its proto node macro
|
||||
DocumentNodeDefinition {
|
||||
@@ -110,7 +111,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
document_node: DocumentNode {
|
||||
implementation: DocumentNodeImplementation::ProtoNode(memo::monitor::IDENTIFIER),
|
||||
inputs: vec![NodeInput::value(TaggedValue::None, true)],
|
||||
manual_composition: Some(generic!(T)),
|
||||
call_argument: generic!(T),
|
||||
skip_deduplication: true,
|
||||
..Default::default()
|
||||
},
|
||||
@@ -150,19 +151,19 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::network(generic!(T), 0)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(memo::memo::IDENTIFIER),
|
||||
manual_composition: Some(generic!(T)),
|
||||
call_argument: generic!(T),
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::node(NodeId(0), 0)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(transform_nodes::freeze_real_time::IDENTIFIER),
|
||||
manual_composition: Some(generic!(T)),
|
||||
call_argument: generic!(T),
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::node(NodeId(1), 0)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(transform_nodes::boundless_footprint::IDENTIFIER),
|
||||
manual_composition: Some(generic!(T)),
|
||||
call_argument: generic!(T),
|
||||
..Default::default()
|
||||
},
|
||||
]
|
||||
@@ -226,38 +227,41 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
node_template: NodeTemplate {
|
||||
document_node: DocumentNode {
|
||||
implementation: DocumentNodeImplementation::Network(NodeNetwork {
|
||||
exports: vec![NodeInput::node(NodeId(3), 0)],
|
||||
exports: vec![NodeInput::node(NodeId(4), 0)],
|
||||
nodes: [
|
||||
// Secondary (left) input type coercion
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::network(generic!(T), 1)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(graphic_element::to_element::IDENTIFIER),
|
||||
manual_composition: Some(concrete!(Context)),
|
||||
..Default::default()
|
||||
},
|
||||
// Primary (bottom) input type coercion
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::network(generic!(T), 0)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(graphic_element::to_group::IDENTIFIER),
|
||||
manual_composition: Some(concrete!(Context)),
|
||||
implementation: DocumentNodeImplementation::ProtoNode(graphic::to_graphic::IDENTIFIER),
|
||||
call_argument: concrete!(Context),
|
||||
..Default::default()
|
||||
},
|
||||
// Secondary (left) input type coercion
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::network(generic!(T), 1)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(graphic::wrap_graphic::IDENTIFIER),
|
||||
call_argument: concrete!(Context),
|
||||
..Default::default()
|
||||
},
|
||||
// Store the ID of the parent node (which encapsulates this sub-network) in each row we are extending the table with.
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::node(NodeId(1), 0), NodeInput::Reflection(graph_craft::document::DocumentNodeMetadata::DocumentNodePath)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(graphic::source_node_id::IDENTIFIER),
|
||||
call_argument: concrete!(Context),
|
||||
..Default::default()
|
||||
},
|
||||
// The monitor node is used to display a thumbnail in the UI
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::node(NodeId(0), 0)],
|
||||
inputs: vec![NodeInput::node(NodeId(2), 0)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(memo::monitor::IDENTIFIER),
|
||||
manual_composition: Some(concrete!(Context)),
|
||||
call_argument: concrete!(Context),
|
||||
skip_deduplication: true,
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNode {
|
||||
manual_composition: Some(generic!(T)),
|
||||
inputs: vec![
|
||||
NodeInput::node(NodeId(1), 0),
|
||||
NodeInput::node(NodeId(2), 0),
|
||||
NodeInput::Reflection(graph_craft::document::DocumentNodeMetadata::DocumentNodePath),
|
||||
],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(graphic_element::layer::IDENTIFIER),
|
||||
call_argument: generic!(T),
|
||||
inputs: vec![NodeInput::node(NodeId(0), 0), NodeInput::node(NodeId(3), 0)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(graphic::extend::IDENTIFIER),
|
||||
..Default::default()
|
||||
},
|
||||
]
|
||||
@@ -268,13 +272,13 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
..Default::default()
|
||||
}),
|
||||
inputs: vec![
|
||||
NodeInput::value(TaggedValue::GraphicGroup(GraphicGroupTable::default()), true),
|
||||
NodeInput::value(TaggedValue::GraphicGroup(GraphicGroupTable::default()), true),
|
||||
NodeInput::value(TaggedValue::Graphic(Default::default()), true),
|
||||
NodeInput::value(TaggedValue::Graphic(Default::default()), true),
|
||||
],
|
||||
..Default::default()
|
||||
},
|
||||
persistent_node_metadata: DocumentNodePersistentMetadata {
|
||||
input_metadata: vec![("Graphical Data", "TODO").into(), ("Over", "TODO").into()],
|
||||
input_metadata: vec![("Base", "TODO").into(), ("Content", "TODO").into()],
|
||||
output_names: vec!["Out".to_string()],
|
||||
node_type_metadata: NodeTypePersistentMetadata::layer(IVec2::new(0, 0)),
|
||||
network_metadata: Some(NodeNetworkMetadata {
|
||||
@@ -282,16 +286,24 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
node_metadata: [
|
||||
DocumentNodeMetadata {
|
||||
persistent_metadata: DocumentNodePersistentMetadata {
|
||||
display_name: "To Element".to_string(),
|
||||
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(-14, -1)),
|
||||
display_name: "To Graphic".to_string(),
|
||||
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(-21, -3)),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNodeMetadata {
|
||||
persistent_metadata: DocumentNodePersistentMetadata {
|
||||
display_name: "To Group".to_string(),
|
||||
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(-14, -3)),
|
||||
display_name: "Wrap Graphic".to_string(),
|
||||
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(-21, -1)),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNodeMetadata {
|
||||
persistent_metadata: DocumentNodePersistentMetadata {
|
||||
display_name: "Source Node ID".to_string(),
|
||||
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(-14, -1)),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
@@ -306,7 +318,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
},
|
||||
DocumentNodeMetadata {
|
||||
persistent_metadata: DocumentNodePersistentMetadata {
|
||||
display_name: "Layer".to_string(),
|
||||
display_name: "Extend".to_string(),
|
||||
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, -3)),
|
||||
..Default::default()
|
||||
},
|
||||
@@ -324,7 +336,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
..Default::default()
|
||||
},
|
||||
},
|
||||
description: Cow::Borrowed("Merge attaches a layer to the stack's group."),
|
||||
description: Cow::Borrowed("Merges new content as an entry into the graphic table that represents a layer compositing stack."),
|
||||
properties: None,
|
||||
},
|
||||
DocumentNodeDefinition {
|
||||
@@ -333,12 +345,12 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
node_template: NodeTemplate {
|
||||
document_node: DocumentNode {
|
||||
implementation: DocumentNodeImplementation::Network(NodeNetwork {
|
||||
exports: vec![NodeInput::node(NodeId(2), 0)],
|
||||
exports: vec![NodeInput::node(NodeId(3), 0)],
|
||||
nodes: [
|
||||
// Ensure this ID is kept in sync with the ID in set_alias so that the name input is kept in sync with the alias
|
||||
DocumentNode {
|
||||
manual_composition: Some(generic!(T)),
|
||||
implementation: DocumentNodeImplementation::ProtoNode(graphic_element::to_artboard::IDENTIFIER),
|
||||
call_argument: generic!(T),
|
||||
implementation: DocumentNodeImplementation::ProtoNode(artboard::create_artboard::IDENTIFIER),
|
||||
inputs: vec![
|
||||
NodeInput::network(concrete!(TaggedValue), 1),
|
||||
NodeInput::value(TaggedValue::String(String::from("Artboard")), false),
|
||||
@@ -349,23 +361,29 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
],
|
||||
..Default::default()
|
||||
},
|
||||
// Store the ID of the parent node (which encapsulates this sub-network) in each row we are extending the table with.
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::node(NodeId(0), 0), NodeInput::Reflection(graph_craft::document::DocumentNodeMetadata::DocumentNodePath)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(graphic::source_node_id::IDENTIFIER),
|
||||
call_argument: concrete!(Context),
|
||||
..Default::default()
|
||||
},
|
||||
// The monitor node is used to display a thumbnail in the UI.
|
||||
// TODO: Check if thumbnail is reversed
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::node(NodeId(0), 0)],
|
||||
inputs: vec![NodeInput::node(NodeId(1), 0)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(memo::monitor::IDENTIFIER),
|
||||
manual_composition: Some(generic!(T)),
|
||||
call_argument: generic!(T),
|
||||
skip_deduplication: true,
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNode {
|
||||
manual_composition: Some(concrete!(Context)),
|
||||
call_argument: concrete!(Context),
|
||||
inputs: vec![
|
||||
NodeInput::network(graphene_std::Type::Fn(Box::new(concrete!(Context)), Box::new(concrete!(ArtboardGroupTable))), 0),
|
||||
NodeInput::node(NodeId(1), 0),
|
||||
NodeInput::Reflection(graph_craft::document::DocumentNodeMetadata::DocumentNodePath),
|
||||
NodeInput::network(graphene_std::Type::Fn(Box::new(concrete!(Context)), Box::new(concrete!(Table<Artboard>))), 0),
|
||||
NodeInput::node(NodeId(2), 0),
|
||||
],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(graphic_element::append_artboard::IDENTIFIER),
|
||||
implementation: DocumentNodeImplementation::ProtoNode(graphic::extend::IDENTIFIER),
|
||||
..Default::default()
|
||||
},
|
||||
]
|
||||
@@ -376,19 +394,19 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
..Default::default()
|
||||
}),
|
||||
inputs: vec![
|
||||
NodeInput::value(TaggedValue::ArtboardGroup(ArtboardGroupTable::default()), true),
|
||||
NodeInput::value(TaggedValue::GraphicGroup(GraphicGroupTable::default()), true),
|
||||
NodeInput::value(TaggedValue::Artboard(Default::default()), true),
|
||||
NodeInput::value(TaggedValue::Graphic(Default::default()), true),
|
||||
NodeInput::value(TaggedValue::DVec2(DVec2::ZERO), false),
|
||||
NodeInput::value(TaggedValue::DVec2(DVec2::new(1920., 1080.)), false),
|
||||
NodeInput::value(TaggedValue::Color(Color::WHITE), false),
|
||||
NodeInput::value(TaggedValue::Color(Table::new_from_element(Color::WHITE)), false),
|
||||
NodeInput::value(TaggedValue::Bool(false), false),
|
||||
],
|
||||
..Default::default()
|
||||
},
|
||||
persistent_node_metadata: DocumentNodePersistentMetadata {
|
||||
input_metadata: vec![
|
||||
("Artboards", "TODO").into(),
|
||||
InputMetadata::with_name_description_override("Contents", "TODO", WidgetOverride::Hidden),
|
||||
("Base", "TODO").into(),
|
||||
InputMetadata::with_name_description_override("Content", "TODO", WidgetOverride::Hidden),
|
||||
InputMetadata::with_name_description_override(
|
||||
"Location",
|
||||
"TODO",
|
||||
@@ -421,7 +439,15 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
node_metadata: [
|
||||
DocumentNodeMetadata {
|
||||
persistent_metadata: DocumentNodePersistentMetadata {
|
||||
display_name: "To Artboard".to_string(),
|
||||
display_name: "Create Artboard".to_string(),
|
||||
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(-21, -3)),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNodeMetadata {
|
||||
persistent_metadata: DocumentNodePersistentMetadata {
|
||||
display_name: "Source Node ID".to_string(),
|
||||
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(-14, -3)),
|
||||
..Default::default()
|
||||
},
|
||||
@@ -437,7 +463,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
},
|
||||
DocumentNodeMetadata {
|
||||
persistent_metadata: DocumentNodePersistentMetadata {
|
||||
display_name: "Append Artboards".to_string(),
|
||||
display_name: "Extend".to_string(),
|
||||
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, -4)),
|
||||
..Default::default()
|
||||
},
|
||||
@@ -468,13 +494,13 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
nodes: [
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::value(TaggedValue::None, false), NodeInput::scope("editor-api"), NodeInput::network(concrete!(String), 1)],
|
||||
manual_composition: Some(concrete!(Context)),
|
||||
call_argument: concrete!(Context),
|
||||
implementation: DocumentNodeImplementation::ProtoNode(wasm_application_io::load_resource::IDENTIFIER),
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::node(NodeId(0), 0)],
|
||||
manual_composition: Some(concrete!(Context)),
|
||||
call_argument: concrete!(Context),
|
||||
implementation: DocumentNodeImplementation::ProtoNode(wasm_application_io::decode_image::IDENTIFIER),
|
||||
..Default::default()
|
||||
},
|
||||
@@ -541,7 +567,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNode {
|
||||
manual_composition: Some(concrete!(Context)),
|
||||
call_argument: concrete!(Context),
|
||||
inputs: vec![NodeInput::node(NodeId(0), 0)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(memo::memo::IDENTIFIER),
|
||||
..Default::default()
|
||||
@@ -591,7 +617,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
description: Cow::Borrowed("Creates a new canvas object."),
|
||||
properties: None,
|
||||
},
|
||||
#[cfg(all(feature = "gpu", target_arch = "wasm32"))]
|
||||
#[cfg(all(feature = "gpu", target_family = "wasm"))]
|
||||
DocumentNodeDefinition {
|
||||
identifier: "Rasterize",
|
||||
category: "Raster",
|
||||
@@ -603,20 +629,20 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::scope("editor-api")],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(wasm_application_io::create_surface::IDENTIFIER),
|
||||
manual_composition: Some(concrete!(Context)),
|
||||
call_argument: concrete!(Context),
|
||||
skip_deduplication: true,
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::node(NodeId(0), 0)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(memo::memo::IDENTIFIER),
|
||||
manual_composition: Some(concrete!(Context)),
|
||||
call_argument: concrete!(Context),
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::network(generic!(T), 0), NodeInput::network(concrete!(Footprint), 1), NodeInput::node(NodeId(1), 0)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(wasm_application_io::rasterize::IDENTIFIER),
|
||||
manual_composition: Some(concrete!(Context)),
|
||||
call_argument: concrete!(Context),
|
||||
..Default::default()
|
||||
},
|
||||
]
|
||||
@@ -627,11 +653,11 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
..Default::default()
|
||||
}),
|
||||
inputs: vec![
|
||||
NodeInput::value(TaggedValue::VectorData(VectorDataTable::default()), true),
|
||||
NodeInput::value(TaggedValue::Vector(Default::default()), true),
|
||||
NodeInput::value(
|
||||
TaggedValue::Footprint(Footprint {
|
||||
transform: DAffine2::from_scale_angle_translation(DVec2::new(100., 100.), 0., DVec2::new(0., 0.)),
|
||||
resolution: UVec2::new(100, 100),
|
||||
transform: DAffine2::from_scale_angle_translation(DVec2::new(1000., 1000.), 0., DVec2::new(0., 0.)),
|
||||
resolution: UVec2::new(1000, 1000),
|
||||
..Default::default()
|
||||
}),
|
||||
false,
|
||||
@@ -681,7 +707,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
..Default::default()
|
||||
},
|
||||
},
|
||||
description: Cow::Borrowed("Rasterizes the given vector data"),
|
||||
description: Cow::Borrowed("TODO"),
|
||||
properties: None,
|
||||
},
|
||||
DocumentNodeDefinition {
|
||||
@@ -689,7 +715,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
category: "Raster: Pattern",
|
||||
node_template: NodeTemplate {
|
||||
document_node: DocumentNode {
|
||||
manual_composition: Some(concrete!(Context)),
|
||||
call_argument: concrete!(Context),
|
||||
implementation: DocumentNodeImplementation::ProtoNode(raster_nodes::std_nodes::noise_pattern::IDENTIFIER),
|
||||
inputs: vec![
|
||||
NodeInput::value(TaggedValue::None, false),
|
||||
@@ -744,6 +770,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
document_node: DocumentNode {
|
||||
implementation: DocumentNodeImplementation::Network(NodeNetwork {
|
||||
exports: vec![
|
||||
NodeInput::value(TaggedValue::None, false),
|
||||
NodeInput::node(NodeId(0), 0),
|
||||
NodeInput::node(NodeId(1), 0),
|
||||
NodeInput::node(NodeId(2), 0),
|
||||
@@ -752,38 +779,38 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
nodes: [
|
||||
DocumentNode {
|
||||
inputs: vec![
|
||||
NodeInput::network(concrete!(RasterDataTable<CPU>), 0),
|
||||
NodeInput::network(concrete!(Table<Raster<CPU>>), 0),
|
||||
NodeInput::value(TaggedValue::RedGreenBlueAlpha(RedGreenBlueAlpha::Red), false),
|
||||
],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(raster_nodes::adjustments::extract_channel::IDENTIFIER),
|
||||
manual_composition: Some(generic!(T)),
|
||||
call_argument: generic!(T),
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNode {
|
||||
inputs: vec![
|
||||
NodeInput::network(concrete!(RasterDataTable<CPU>), 0),
|
||||
NodeInput::network(concrete!(Table<Raster<CPU>>), 0),
|
||||
NodeInput::value(TaggedValue::RedGreenBlueAlpha(RedGreenBlueAlpha::Green), false),
|
||||
],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(raster_nodes::adjustments::extract_channel::IDENTIFIER),
|
||||
manual_composition: Some(generic!(T)),
|
||||
call_argument: generic!(T),
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNode {
|
||||
inputs: vec![
|
||||
NodeInput::network(concrete!(RasterDataTable<CPU>), 0),
|
||||
NodeInput::network(concrete!(Table<Raster<CPU>>), 0),
|
||||
NodeInput::value(TaggedValue::RedGreenBlueAlpha(RedGreenBlueAlpha::Blue), false),
|
||||
],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(raster_nodes::adjustments::extract_channel::IDENTIFIER),
|
||||
manual_composition: Some(generic!(T)),
|
||||
call_argument: generic!(T),
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNode {
|
||||
inputs: vec![
|
||||
NodeInput::network(concrete!(RasterDataTable<CPU>), 0),
|
||||
NodeInput::network(concrete!(Table<Raster<CPU>>), 0),
|
||||
NodeInput::value(TaggedValue::RedGreenBlueAlpha(RedGreenBlueAlpha::Alpha), false),
|
||||
],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(raster_nodes::adjustments::extract_channel::IDENTIFIER),
|
||||
manual_composition: Some(generic!(T)),
|
||||
call_argument: generic!(T),
|
||||
..Default::default()
|
||||
},
|
||||
]
|
||||
@@ -793,13 +820,12 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
.collect(),
|
||||
..Default::default()
|
||||
}),
|
||||
inputs: vec![NodeInput::value(TaggedValue::RasterData(RasterDataTable::default()), true)],
|
||||
inputs: vec![NodeInput::value(TaggedValue::Raster(Default::default()), true)],
|
||||
..Default::default()
|
||||
},
|
||||
persistent_node_metadata: DocumentNodePersistentMetadata {
|
||||
input_metadata: vec![("Image", "TODO").into()],
|
||||
output_names: vec!["Red".to_string(), "Green".to_string(), "Blue".to_string(), "Alpha".to_string()],
|
||||
has_primary_output: false,
|
||||
output_names: vec!["".to_string(), "Red".to_string(), "Green".to_string(), "Blue".to_string(), "Alpha".to_string()],
|
||||
network_metadata: Some(NodeNetworkMetadata {
|
||||
persistent_metadata: NodeNetworkPersistentMetadata {
|
||||
node_metadata: [
|
||||
@@ -851,23 +877,23 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
properties: None,
|
||||
},
|
||||
DocumentNodeDefinition {
|
||||
identifier: "Split Coordinate",
|
||||
identifier: "Split Vec2",
|
||||
category: "Math: Vector",
|
||||
node_template: NodeTemplate {
|
||||
document_node: DocumentNode {
|
||||
implementation: DocumentNodeImplementation::Network(NodeNetwork {
|
||||
exports: vec![NodeInput::node(NodeId(0), 0), NodeInput::node(NodeId(1), 0)],
|
||||
exports: vec![NodeInput::value(TaggedValue::None, false), NodeInput::node(NodeId(0), 0), NodeInput::node(NodeId(1), 0)],
|
||||
nodes: [
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::network(concrete!(RasterDataTable<CPU>), 0), NodeInput::value(TaggedValue::XY(XY::X), false)],
|
||||
inputs: vec![NodeInput::network(concrete!(Table<Raster<CPU>>), 0), NodeInput::value(TaggedValue::XY(XY::X), false)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(extract_xy::extract_xy::IDENTIFIER),
|
||||
manual_composition: Some(generic!(T)),
|
||||
call_argument: generic!(T),
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::network(concrete!(RasterDataTable<CPU>), 0), NodeInput::value(TaggedValue::XY(XY::Y), false)],
|
||||
inputs: vec![NodeInput::network(concrete!(Table<Raster<CPU>>), 0), NodeInput::value(TaggedValue::XY(XY::Y), false)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(extract_xy::extract_xy::IDENTIFIER),
|
||||
manual_composition: Some(generic!(T)),
|
||||
call_argument: generic!(T),
|
||||
..Default::default()
|
||||
},
|
||||
]
|
||||
@@ -878,13 +904,12 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
|
||||
..Default::default()
|
||||
}),
|
||||
inputs: vec![NodeInput::value(TaggedValue::RasterData(RasterDataTable::default()), true)],
|
||||
inputs: vec![NodeInput::value(TaggedValue::DVec2(DVec2::ZERO), true)],
|
||||
..Default::default()
|
||||
},
|
||||
persistent_node_metadata: DocumentNodePersistentMetadata {
|
||||
input_metadata: vec![("Coordinate", "TODO").into()],
|
||||
output_names: vec!["X".to_string(), "Y".to_string()],
|
||||
has_primary_output: false,
|
||||
input_metadata: vec![("Vec2", "TODO").into()],
|
||||
output_names: vec!["".to_string(), "X".to_string(), "Y".to_string()],
|
||||
network_metadata: Some(NodeNetworkMetadata {
|
||||
persistent_metadata: NodeNetworkPersistentMetadata {
|
||||
node_metadata: [
|
||||
@@ -917,7 +942,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
},
|
||||
},
|
||||
description: Cow::Borrowed(
|
||||
"Decomposes the X and Y components of a 2D coordinate.\n\nThe inverse of this node is \"Coordinate Value\", which can have either or both its X and Y exposed as graph inputs.",
|
||||
"Decomposes the X and Y components of a vec2.\n\nThe inverse of this node is \"Vec2 Value\", which can have either or both its X and Y parameters exposed as graph inputs.",
|
||||
),
|
||||
properties: None,
|
||||
},
|
||||
@@ -931,11 +956,11 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
exports: vec![NodeInput::node(NodeId(0), 0)],
|
||||
nodes: vec![DocumentNode {
|
||||
inputs: vec![
|
||||
NodeInput::network(concrete!(RasterDataTable<CPU>), 0),
|
||||
NodeInput::network(concrete!(Table<Raster<CPU>>), 0),
|
||||
NodeInput::network(concrete!(Vec<brush::brush_stroke::BrushStroke>), 1),
|
||||
NodeInput::network(concrete!(BrushCache), 2),
|
||||
],
|
||||
manual_composition: Some(concrete!(Context)),
|
||||
call_argument: concrete!(Context),
|
||||
implementation: DocumentNodeImplementation::ProtoNode(brush::brush::brush::IDENTIFIER),
|
||||
..Default::default()
|
||||
}]
|
||||
@@ -946,7 +971,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
..Default::default()
|
||||
}),
|
||||
inputs: vec![
|
||||
NodeInput::value(TaggedValue::RasterData(RasterDataTable::default()), true),
|
||||
NodeInput::value(TaggedValue::Raster(Default::default()), true),
|
||||
NodeInput::value(TaggedValue::BrushStrokes(Vec::new()), false),
|
||||
NodeInput::value(TaggedValue::BrushCache(BrushCache::default()), false),
|
||||
],
|
||||
@@ -985,8 +1010,8 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
node_template: NodeTemplate {
|
||||
document_node: DocumentNode {
|
||||
implementation: DocumentNodeImplementation::ProtoNode(memo::memo::IDENTIFIER),
|
||||
inputs: vec![NodeInput::value(TaggedValue::RasterData(RasterDataTable::default()), true)],
|
||||
manual_composition: Some(concrete!(Context)),
|
||||
inputs: vec![NodeInput::value(TaggedValue::Raster(Default::default()), true)],
|
||||
call_argument: concrete!(Context),
|
||||
..Default::default()
|
||||
},
|
||||
persistent_node_metadata: DocumentNodePersistentMetadata {
|
||||
@@ -1004,8 +1029,8 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
node_template: NodeTemplate {
|
||||
document_node: DocumentNode {
|
||||
implementation: DocumentNodeImplementation::ProtoNode(memo::impure_memo::IDENTIFIER),
|
||||
inputs: vec![NodeInput::value(TaggedValue::RasterData(RasterDataTable::default()), true)],
|
||||
manual_composition: Some(concrete!(Context)),
|
||||
inputs: vec![NodeInput::value(TaggedValue::Raster(Default::default()), true)],
|
||||
call_argument: concrete!(Context),
|
||||
..Default::default()
|
||||
},
|
||||
persistent_node_metadata: DocumentNodePersistentMetadata {
|
||||
@@ -1027,13 +1052,13 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
exports: vec![NodeInput::node(NodeId(1), 0)],
|
||||
nodes: [
|
||||
DocumentNode {
|
||||
manual_composition: Some(concrete!(Context)),
|
||||
call_argument: concrete!(Context),
|
||||
inputs: vec![NodeInput::scope("editor-api")],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(wgpu_executor::create_gpu_surface::IDENTIFIER),
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNode {
|
||||
manual_composition: Some(concrete!(Context)),
|
||||
call_argument: concrete!(Context),
|
||||
inputs: vec![NodeInput::node(NodeId(0), 0)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(memo::impure_memo::IDENTIFIER),
|
||||
..Default::default()
|
||||
@@ -1083,6 +1108,86 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
description: Cow::Borrowed("TODO"),
|
||||
properties: None,
|
||||
},
|
||||
#[cfg(feature = "gpu")]
|
||||
DocumentNodeDefinition {
|
||||
identifier: "Upload Texture",
|
||||
category: "Debug: GPU",
|
||||
node_template: NodeTemplate {
|
||||
document_node: DocumentNode {
|
||||
implementation: DocumentNodeImplementation::Network(NodeNetwork {
|
||||
exports: vec![NodeInput::node(NodeId(2), 0)],
|
||||
nodes: [
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::scope("editor-api")],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::ops::IntoNode<&WgpuExecutor>")),
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::network(concrete!(Table<Raster<CPU>>), 0), NodeInput::node(NodeId(0), 0)],
|
||||
call_argument: generic!(T),
|
||||
implementation: DocumentNodeImplementation::ProtoNode(wgpu_executor::texture_upload::upload_texture::IDENTIFIER),
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNode {
|
||||
call_argument: generic!(T),
|
||||
inputs: vec![NodeInput::node(NodeId(1), 0)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(memo::impure_memo::IDENTIFIER),
|
||||
..Default::default()
|
||||
},
|
||||
]
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(id, node)| (NodeId(id as u64), node))
|
||||
.collect(),
|
||||
..Default::default()
|
||||
}),
|
||||
inputs: vec![NodeInput::value(TaggedValue::Raster(Default::default()), true)],
|
||||
..Default::default()
|
||||
},
|
||||
persistent_node_metadata: DocumentNodePersistentMetadata {
|
||||
output_names: vec!["Texture".to_string()],
|
||||
network_metadata: Some(NodeNetworkMetadata {
|
||||
persistent_metadata: NodeNetworkPersistentMetadata {
|
||||
node_metadata: [
|
||||
DocumentNodeMetadata {
|
||||
persistent_metadata: DocumentNodePersistentMetadata {
|
||||
display_name: "Extract Executor".to_string(),
|
||||
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, 0)),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNodeMetadata {
|
||||
persistent_metadata: DocumentNodePersistentMetadata {
|
||||
display_name: "Upload Texture".to_string(),
|
||||
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(7, 0)),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNodeMetadata {
|
||||
persistent_metadata: DocumentNodePersistentMetadata {
|
||||
display_name: "Cache".to_string(),
|
||||
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(14, 0)),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
]
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(id, node)| (NodeId(id as u64), node))
|
||||
.collect(),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
}),
|
||||
..Default::default()
|
||||
},
|
||||
},
|
||||
description: Cow::Borrowed("TODO"),
|
||||
properties: None,
|
||||
},
|
||||
DocumentNodeDefinition {
|
||||
identifier: "Extract",
|
||||
category: "Debug",
|
||||
@@ -1115,7 +1220,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
// document_node: DocumentNode {
|
||||
// implementation: DocumentNodeImplementation::proto("graphene_core::raster::CurvesNode"),
|
||||
// inputs: vec![
|
||||
// NodeInput::value(TaggedValue::RasterData(RasterDataTable::default()), true),
|
||||
// NodeInput::value(TaggedValue::Raster(Default::default()), true),
|
||||
// NodeInput::value(TaggedValue::Curve(Default::default()), false),
|
||||
// ],
|
||||
// ..Default::default()
|
||||
@@ -1138,9 +1243,9 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
exports: vec![NodeInput::node(NodeId(1), 0)],
|
||||
nodes: vec![
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::network(concrete!(VectorDataTable), 0)],
|
||||
inputs: vec![NodeInput::network(concrete!(Table<Vector>), 0)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(memo::monitor::IDENTIFIER),
|
||||
manual_composition: Some(generic!(T)),
|
||||
call_argument: generic!(T),
|
||||
skip_deduplication: true,
|
||||
..Default::default()
|
||||
},
|
||||
@@ -1150,7 +1255,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
NodeInput::network(concrete!(graphene_std::vector::VectorModification), 1),
|
||||
NodeInput::Reflection(graph_craft::document::DocumentNodeMetadata::DocumentNodePath),
|
||||
],
|
||||
manual_composition: Some(generic!(T)),
|
||||
call_argument: generic!(T),
|
||||
implementation: DocumentNodeImplementation::ProtoNode(vector::path_modify::IDENTIFIER),
|
||||
..Default::default()
|
||||
},
|
||||
@@ -1162,14 +1267,14 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
..Default::default()
|
||||
}),
|
||||
inputs: vec![
|
||||
NodeInput::value(TaggedValue::VectorData(VectorDataTable::default()), true),
|
||||
NodeInput::value(TaggedValue::Vector(Default::default()), true),
|
||||
NodeInput::value(TaggedValue::VectorModification(Default::default()), false),
|
||||
],
|
||||
..Default::default()
|
||||
},
|
||||
persistent_node_metadata: DocumentNodePersistentMetadata {
|
||||
input_metadata: vec![("Vector Data", "TODO").into(), ("Modification", "TODO").into()],
|
||||
output_names: vec!["Vector Data".to_string()],
|
||||
input_metadata: vec![("Content", "TODO").into(), ("Modification", "TODO").into()],
|
||||
output_names: vec!["Modified".to_string()],
|
||||
network_metadata: Some(NodeNetworkMetadata {
|
||||
persistent_metadata: NodeNetworkPersistentMetadata {
|
||||
node_metadata: [
|
||||
@@ -1210,7 +1315,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
node_template: NodeTemplate {
|
||||
document_node: DocumentNode {
|
||||
implementation: DocumentNodeImplementation::ProtoNode(text::text::IDENTIFIER),
|
||||
manual_composition: Some(concrete!(Context)),
|
||||
call_argument: concrete!(Context),
|
||||
inputs: vec![
|
||||
NodeInput::scope("editor-api"),
|
||||
NodeInput::value(TaggedValue::String("Lorem ipsum".to_string()), false),
|
||||
@@ -1224,6 +1329,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
NodeInput::value(TaggedValue::OptionalF64(TypesettingConfig::default().max_width), false),
|
||||
NodeInput::value(TaggedValue::OptionalF64(TypesettingConfig::default().max_height), false),
|
||||
NodeInput::value(TaggedValue::F64(TypesettingConfig::default().tilt), false),
|
||||
NodeInput::value(TaggedValue::TextAlign(text::TextAlign::default()), false),
|
||||
NodeInput::value(TaggedValue::Bool(false), false),
|
||||
],
|
||||
..Default::default()
|
||||
@@ -1257,7 +1363,6 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
"TODO",
|
||||
WidgetOverride::Number(NumberInputSettings {
|
||||
unit: Some(" px".to_string()),
|
||||
min: Some(0.),
|
||||
step: Some(0.1),
|
||||
..Default::default()
|
||||
}),
|
||||
@@ -1292,7 +1397,8 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
..Default::default()
|
||||
}),
|
||||
),
|
||||
("Per-Glyph Instances", "Splits each text glyph into its own instance, i.e. row in the table of vector data.").into(),
|
||||
InputMetadata::with_name_description_override("Align", "TODO", WidgetOverride::Custom("text_align".to_string())),
|
||||
("Per-Glyph Instances", "Splits each text glyph into its own row in the table of vector geometry.").into(),
|
||||
],
|
||||
output_names: vec!["Vector".to_string()],
|
||||
..Default::default()
|
||||
@@ -1319,7 +1425,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::network(generic!(T), 0)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(memo::monitor::IDENTIFIER),
|
||||
manual_composition: Some(generic!(T)),
|
||||
call_argument: generic!(T),
|
||||
skip_deduplication: true,
|
||||
..Default::default()
|
||||
},
|
||||
@@ -1331,7 +1437,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
NodeInput::network(concrete!(DVec2), 3),
|
||||
NodeInput::network(concrete!(DVec2), 4),
|
||||
],
|
||||
manual_composition: Some(concrete!(Context)),
|
||||
call_argument: concrete!(Context),
|
||||
implementation: DocumentNodeImplementation::ProtoNode(transform_nodes::transform::IDENTIFIER),
|
||||
..Default::default()
|
||||
},
|
||||
@@ -1414,27 +1520,27 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
exports: vec![NodeInput::node(NodeId(3), 0)],
|
||||
nodes: vec![
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::network(concrete!(VectorDataTable), 0), NodeInput::network(concrete!(vector::style::Fill), 1)],
|
||||
inputs: vec![NodeInput::network(concrete!(Table<Vector>), 0), NodeInput::network(concrete!(vector::style::Fill), 1)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(path_bool::boolean_operation::IDENTIFIER),
|
||||
manual_composition: Some(generic!(T)),
|
||||
call_argument: generic!(T),
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::node(NodeId(0), 0)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(memo::memo::IDENTIFIER),
|
||||
manual_composition: Some(generic!(T)),
|
||||
call_argument: generic!(T),
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::node(NodeId(1), 0)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(transform_nodes::freeze_real_time::IDENTIFIER),
|
||||
manual_composition: Some(generic!(T)),
|
||||
call_argument: generic!(T),
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::node(NodeId(2), 0)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(transform_nodes::boundless_footprint::IDENTIFIER),
|
||||
manual_composition: Some(generic!(T)),
|
||||
call_argument: generic!(T),
|
||||
..Default::default()
|
||||
},
|
||||
]
|
||||
@@ -1445,7 +1551,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
..Default::default()
|
||||
}),
|
||||
inputs: vec![
|
||||
NodeInput::value(TaggedValue::GraphicGroup(GraphicGroupTable::default()), true),
|
||||
NodeInput::value(TaggedValue::Graphic(Default::default()), true),
|
||||
NodeInput::value(TaggedValue::BooleanOperation(path_bool::BooleanOperation::Union), false),
|
||||
],
|
||||
..Default::default()
|
||||
@@ -1495,7 +1601,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
},
|
||||
..Default::default()
|
||||
}),
|
||||
input_metadata: vec![("Group of Paths", "TODO").into(), ("Operation", "TODO").into()],
|
||||
input_metadata: vec![("Content", "TODO").into(), ("Operation", "TODO").into()],
|
||||
output_names: vec!["Vector".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
@@ -1512,14 +1618,14 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
exports: vec![NodeInput::node(NodeId(4), 0)],
|
||||
nodes: [
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::network(concrete!(graphene_std::vector::VectorDataTable), 0)],
|
||||
inputs: vec![NodeInput::network(concrete!(Table<Vector>), 0)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(vector::subpath_segment_lengths::IDENTIFIER),
|
||||
manual_composition: Some(generic!(T)),
|
||||
call_argument: generic!(T),
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNode {
|
||||
inputs: vec![
|
||||
NodeInput::network(concrete!(graphene_std::vector::VectorDataTable), 0),
|
||||
NodeInput::network(concrete!(Table<Vector>), 0),
|
||||
NodeInput::network(concrete!(vector::misc::PointSpacingType), 1),
|
||||
NodeInput::network(concrete!(f64), 2),
|
||||
NodeInput::network(concrete!(u32), 3),
|
||||
@@ -1529,25 +1635,25 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
NodeInput::node(NodeId(0), 0),
|
||||
],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(vector::sample_polyline::IDENTIFIER),
|
||||
manual_composition: Some(generic!(T)),
|
||||
call_argument: generic!(T),
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::node(NodeId(1), 0)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(memo::memo::IDENTIFIER),
|
||||
manual_composition: Some(generic!(T)),
|
||||
call_argument: generic!(T),
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::node(NodeId(2), 0)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(transform_nodes::freeze_real_time::IDENTIFIER),
|
||||
manual_composition: Some(generic!(T)),
|
||||
call_argument: generic!(T),
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::node(NodeId(3), 0)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(transform_nodes::boundless_footprint::IDENTIFIER),
|
||||
manual_composition: Some(generic!(T)),
|
||||
call_argument: generic!(T),
|
||||
..Default::default()
|
||||
},
|
||||
]
|
||||
@@ -1558,7 +1664,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
..Default::default()
|
||||
}),
|
||||
inputs: vec![
|
||||
NodeInput::value(TaggedValue::VectorData(graphene_std::vector::VectorDataTable::default()), true),
|
||||
NodeInput::value(TaggedValue::Vector(Default::default()), true),
|
||||
NodeInput::value(TaggedValue::PointSpacingType(Default::default()), false),
|
||||
NodeInput::value(TaggedValue::F64(100.), false),
|
||||
NodeInput::value(TaggedValue::U32(100), false),
|
||||
@@ -1622,7 +1728,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
..Default::default()
|
||||
}),
|
||||
input_metadata: vec![
|
||||
("Vector Data", "The shape to be resampled and converted into a polyline.").into(),
|
||||
("Content", "The shape to be resampled and converted into a polyline.").into(),
|
||||
("Spacing", node_properties::SAMPLE_POLYLINE_TOOLTIP_SPACING).into(),
|
||||
InputMetadata::with_name_description_override(
|
||||
"Separation",
|
||||
@@ -1679,30 +1785,30 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
nodes: [
|
||||
DocumentNode {
|
||||
inputs: vec![
|
||||
NodeInput::network(concrete!(graphene_std::vector::VectorDataTable), 0),
|
||||
NodeInput::network(concrete!(Table<Vector>), 0),
|
||||
NodeInput::network(concrete!(f64), 1),
|
||||
NodeInput::network(concrete!(u32), 2),
|
||||
],
|
||||
manual_composition: Some(generic!(T)),
|
||||
call_argument: generic!(T),
|
||||
implementation: DocumentNodeImplementation::ProtoNode(vector::poisson_disk_points::IDENTIFIER),
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::node(NodeId(0), 0)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(memo::memo::IDENTIFIER),
|
||||
manual_composition: Some(generic!(T)),
|
||||
call_argument: generic!(T),
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::node(NodeId(1), 0)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(transform_nodes::freeze_real_time::IDENTIFIER),
|
||||
manual_composition: Some(generic!(T)),
|
||||
call_argument: generic!(T),
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::node(NodeId(2), 0)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(transform_nodes::boundless_footprint::IDENTIFIER),
|
||||
manual_composition: Some(generic!(T)),
|
||||
call_argument: generic!(T),
|
||||
..Default::default()
|
||||
},
|
||||
]
|
||||
@@ -1713,7 +1819,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
..Default::default()
|
||||
}),
|
||||
inputs: vec![
|
||||
NodeInput::value(TaggedValue::VectorData(graphene_std::vector::VectorDataTable::default()), true),
|
||||
NodeInput::value(TaggedValue::Vector(Default::default()), true),
|
||||
NodeInput::value(TaggedValue::F64(10.), false),
|
||||
NodeInput::value(TaggedValue::U32(0), false),
|
||||
],
|
||||
@@ -1765,7 +1871,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
..Default::default()
|
||||
}),
|
||||
input_metadata: vec![
|
||||
("Vector Data", "TODO").into(),
|
||||
("Content", "TODO").into(),
|
||||
InputMetadata::with_name_description_override(
|
||||
"Separation Disk Diameter",
|
||||
"TODO",
|
||||
@@ -1817,13 +1923,9 @@ fn static_node_properties() -> NodeProperties {
|
||||
map.insert("rectangle_properties".to_string(), Box::new(node_properties::rectangle_properties));
|
||||
map.insert("grid_properties".to_string(), Box::new(node_properties::grid_properties));
|
||||
map.insert("sample_polyline_properties".to_string(), Box::new(node_properties::sample_polyline_properties));
|
||||
map.insert(
|
||||
"identity_properties".to_string(),
|
||||
Box::new(|_node_id, _context| node_properties::string_properties("The identity node passes its data through.")),
|
||||
);
|
||||
map.insert(
|
||||
"monitor_properties".to_string(),
|
||||
Box::new(|_node_id, _context| node_properties::string_properties("The Monitor node is used by the editor to access the data flowing through it.")),
|
||||
Box::new(|_node_id, _context| node_properties::string_properties("Used internally by the editor to obtain a layer thumbnail.")),
|
||||
);
|
||||
map
|
||||
}
|
||||
@@ -1840,10 +1942,10 @@ fn static_input_properties() -> InputProperties {
|
||||
"string".to_string(),
|
||||
Box::new(|node_id, index, context| {
|
||||
let Some(value) = context.network_interface.input_data(&node_id, index, "string_properties", context.selection_network_path) else {
|
||||
return Err(format!("Could not get string properties for node {}", node_id));
|
||||
return Err(format!("Could not get string properties for node {node_id}"));
|
||||
};
|
||||
let Some(string) = value.as_str() else {
|
||||
return Err(format!("Could not downcast string properties for node {}", node_id));
|
||||
return Err(format!("Could not downcast string properties for node {node_id}"));
|
||||
};
|
||||
Ok(node_properties::string_properties(string))
|
||||
}),
|
||||
@@ -1961,7 +2063,7 @@ fn static_input_properties() -> InputProperties {
|
||||
.and_then(|value| value.as_bool())
|
||||
.unwrap_or_default();
|
||||
|
||||
Ok(vec![node_properties::coordinate_widget(
|
||||
Ok(vec![node_properties::vec2_widget(
|
||||
ParameterWidgetsInfo::new(node_id, index, true, context),
|
||||
&x,
|
||||
&y,
|
||||
@@ -2217,7 +2319,7 @@ fn static_input_properties() -> InputProperties {
|
||||
"spline_input".to_string(),
|
||||
Box::new(|node_id, index, context| {
|
||||
Ok(vec![LayoutGroup::Row {
|
||||
widgets: node_properties::array_of_coordinates_widget(ParameterWidgetsInfo::new(node_id, index, true, context), TextInput::default().centered(true)),
|
||||
widgets: node_properties::array_of_vec2_widget(ParameterWidgetsInfo::new(node_id, index, true, context), TextInput::default().centered(true)),
|
||||
}])
|
||||
}),
|
||||
);
|
||||
@@ -2324,6 +2426,13 @@ fn static_input_properties() -> InputProperties {
|
||||
)])
|
||||
}),
|
||||
);
|
||||
map.insert(
|
||||
"text_align".to_string(),
|
||||
Box::new(|node_id, index, context| {
|
||||
let choices = enum_choice::<text::TextAlign>().for_socket(ParameterWidgetsInfo::new(node_id, index, true, context)).property_row();
|
||||
Ok(vec![choices])
|
||||
}),
|
||||
);
|
||||
map
|
||||
}
|
||||
|
||||
|
||||
+2
-3
@@ -59,8 +59,8 @@ pub(super) fn post_process_nodes(mut custom: Vec<DocumentNodeDefinition>) -> Vec
|
||||
node_template: NodeTemplate {
|
||||
document_node: DocumentNode {
|
||||
inputs,
|
||||
manual_composition: Some(input_type.clone()),
|
||||
implementation: DocumentNodeImplementation::ProtoNode(id.clone().into()),
|
||||
call_argument: (input_type.clone()),
|
||||
implementation: DocumentNodeImplementation::ProtoNode(id.clone()),
|
||||
visible: true,
|
||||
skip_deduplication: false,
|
||||
..Default::default()
|
||||
@@ -77,7 +77,6 @@ pub(super) fn post_process_nodes(mut custom: Vec<DocumentNodeDefinition>) -> Vec
|
||||
})
|
||||
.collect(),
|
||||
output_names: vec![output_type.to_string()],
|
||||
has_primary_output: true,
|
||||
locked: false,
|
||||
..Default::default()
|
||||
},
|
||||
|
||||
@@ -18,7 +18,11 @@ pub enum NodeGraphMessage {
|
||||
},
|
||||
AddPathNode,
|
||||
AddImport,
|
||||
AddPrimaryImport,
|
||||
AddSecondaryImport,
|
||||
AddExport,
|
||||
AddPrimaryExport,
|
||||
AddSecondaryExport,
|
||||
Init,
|
||||
SelectedNodesUpdated,
|
||||
Copy,
|
||||
@@ -63,6 +67,12 @@ pub enum NodeGraphMessage {
|
||||
set_to_exposed: bool,
|
||||
start_transaction: bool,
|
||||
},
|
||||
ExposeEncapsulatingPrimaryInput {
|
||||
exposed: bool,
|
||||
},
|
||||
ExposePrimaryExport {
|
||||
exposed: bool,
|
||||
},
|
||||
InsertNode {
|
||||
node_id: NodeId,
|
||||
node_template: NodeTemplate,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use super::utility_types::{BoxSelection, ContextMenuInformation, DragStart, FrontendGraphInput, FrontendGraphOutput, FrontendNode};
|
||||
use super::utility_types::{BoxSelection, ContextMenuInformation, DragStart, FrontendNode};
|
||||
use super::{document_node_definitions, node_properties};
|
||||
use crate::consts::GRID_SIZE;
|
||||
use crate::messages::input_mapper::utility_types::macros::action_keys;
|
||||
@@ -17,17 +17,16 @@ use crate::messages::portfolio::document::utility_types::wires::{GraphWireStyle,
|
||||
use crate::messages::prelude::*;
|
||||
use crate::messages::tool::common_functionality::auto_panning::AutoPanning;
|
||||
use crate::messages::tool::common_functionality::graph_modification_utils::{self, get_clip_mode};
|
||||
use crate::messages::tool::common_functionality::utility_functions::make_path_editable_is_allowed;
|
||||
use crate::messages::tool::tool_messages::tool_prelude::{Key, MouseMotion};
|
||||
use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo};
|
||||
use bezier_rs::Subpath;
|
||||
use glam::{DAffine2, DVec2, IVec2};
|
||||
use graph_craft::document::value::TaggedValue;
|
||||
use graph_craft::document::{DocumentNodeImplementation, NodeId, NodeInput};
|
||||
use graph_craft::proto::GraphErrors;
|
||||
use graphene_std::math::math_ext::QuadExt;
|
||||
use graphene_std::vector::misc::subpath_to_kurbo_bezpath;
|
||||
use graphene_std::vector::algorithms::bezpath_algorithms::bezpath_is_inside_bezpath;
|
||||
use graphene_std::*;
|
||||
use kurbo::{Line, Point};
|
||||
use kurbo::{DEFAULT_ACCURACY, Shape};
|
||||
use renderer::Quad;
|
||||
use std::cmp::Ordering;
|
||||
|
||||
@@ -43,6 +42,7 @@ pub struct NodeGraphMessageContext<'a> {
|
||||
pub graph_fade_artwork_percentage: f64,
|
||||
pub navigation_handler: &'a NavigationMessageHandler,
|
||||
pub preferences: &'a PreferencesMessageHandler,
|
||||
pub layers_panel_open: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, ExtractField)]
|
||||
@@ -89,7 +89,7 @@ pub struct NodeGraphMessageHandler {
|
||||
reordering_import: Option<usize>,
|
||||
/// The index of the export that is being moved
|
||||
reordering_export: Option<usize>,
|
||||
/// The end index of the moved port
|
||||
/// The end index of the moved connector
|
||||
end_index: Option<usize>,
|
||||
/// Used to keep track of what nodes are sent to the front end so that only visible ones are sent to the frontend
|
||||
frontend_nodes: Vec<NodeId>,
|
||||
@@ -112,6 +112,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
|
||||
graph_fade_artwork_percentage,
|
||||
navigation_handler,
|
||||
preferences,
|
||||
layers_panel_open,
|
||||
} = context;
|
||||
|
||||
match message {
|
||||
@@ -126,48 +127,56 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
|
||||
responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![new_layer_id] });
|
||||
}
|
||||
NodeGraphMessage::AddPathNode => {
|
||||
let selected_nodes = network_interface.selected_nodes();
|
||||
let mut selected_layers = selected_nodes.selected_layers(network_interface.document_metadata());
|
||||
let first_layer = selected_layers.next();
|
||||
let second_layer = selected_layers.next();
|
||||
let has_single_selection = first_layer.is_some() && second_layer.is_none();
|
||||
|
||||
let compatible_type = first_layer.and_then(|layer| {
|
||||
let graph_layer = graph_modification_utils::NodeGraphLayer::new(layer, &network_interface);
|
||||
graph_layer.horizontal_layer_flow().nth(1).and_then(|node_id| {
|
||||
let (output_type, _) = network_interface.output_type(&node_id, 0, &[]);
|
||||
Some(format!("type:{}", output_type.nested_type()))
|
||||
})
|
||||
});
|
||||
|
||||
let is_compatible = compatible_type.as_deref() == Some("type:Instances<VectorData>");
|
||||
|
||||
if first_layer.is_some() && has_single_selection && is_compatible {
|
||||
if let Some(layer) = first_layer {
|
||||
let node_type = "Path".to_string();
|
||||
let graph_layer = graph_modification_utils::NodeGraphLayer::new(layer, &network_interface);
|
||||
let is_modifiable = matches!(graph_layer.find_input("Path", 1), Some(TaggedValue::VectorModification(_)));
|
||||
if !is_modifiable {
|
||||
responses.add(NodeGraphMessage::CreateNodeInLayerWithTransaction {
|
||||
node_type: node_type.clone(),
|
||||
layer: LayerNodeIdentifier::new_unchecked(layer.to_node()),
|
||||
});
|
||||
responses.add(BroadcastEvent::SelectionChanged);
|
||||
}
|
||||
}
|
||||
if let Some(layer) = make_path_editable_is_allowed(network_interface) {
|
||||
responses.add(NodeGraphMessage::CreateNodeInLayerWithTransaction { node_type: "Path".to_string(), layer });
|
||||
responses.add(EventMessage::SelectionChanged);
|
||||
}
|
||||
}
|
||||
NodeGraphMessage::AddImport => {
|
||||
network_interface.add_import(graph_craft::document::value::TaggedValue::None, true, -1, "", "", breadcrumb_network_path);
|
||||
responses.add(NodeGraphMessage::SendGraph);
|
||||
}
|
||||
NodeGraphMessage::AddPrimaryImport => {
|
||||
if network_interface.number_of_imports(breadcrumb_network_path) == 0 {
|
||||
responses.add(NodeGraphMessage::AddImport);
|
||||
} else {
|
||||
responses.add(NodeGraphMessage::ExposeEncapsulatingPrimaryInput { exposed: true });
|
||||
}
|
||||
}
|
||||
NodeGraphMessage::AddSecondaryImport => {
|
||||
// If necessary, add a hidden primary import before the secondary import
|
||||
if network_interface.number_of_imports(breadcrumb_network_path) == 0 {
|
||||
responses.add(NodeGraphMessage::AddImport);
|
||||
responses.add(NodeGraphMessage::ExposeEncapsulatingPrimaryInput { exposed: false });
|
||||
}
|
||||
|
||||
// Add the secondary import
|
||||
responses.add(NodeGraphMessage::AddImport);
|
||||
}
|
||||
NodeGraphMessage::AddExport => {
|
||||
network_interface.add_export(graph_craft::document::value::TaggedValue::None, -1, "", breadcrumb_network_path);
|
||||
responses.add(NodeGraphMessage::SendGraph);
|
||||
}
|
||||
NodeGraphMessage::AddPrimaryExport => {
|
||||
if network_interface.number_of_exports(breadcrumb_network_path) == 0 {
|
||||
responses.add(NodeGraphMessage::AddExport);
|
||||
} else {
|
||||
responses.add(NodeGraphMessage::ExposePrimaryExport { exposed: true });
|
||||
}
|
||||
}
|
||||
NodeGraphMessage::AddSecondaryExport => {
|
||||
// If necessary, add a hidden primary import before the secondary import
|
||||
if network_interface.number_of_exports(breadcrumb_network_path) == 0 {
|
||||
responses.add(NodeGraphMessage::AddExport);
|
||||
responses.add(NodeGraphMessage::ExposePrimaryExport { exposed: false });
|
||||
}
|
||||
|
||||
// Add the secondary export
|
||||
responses.add(NodeGraphMessage::AddExport);
|
||||
}
|
||||
NodeGraphMessage::Init => {
|
||||
responses.add(BroadcastMessage::SubscribeEvent {
|
||||
on: BroadcastEvent::SelectionChanged,
|
||||
on: EventMessage::SelectionChanged,
|
||||
send: Box::new(NodeGraphMessage::SelectedNodesUpdated.into()),
|
||||
});
|
||||
network_interface.load_structure();
|
||||
@@ -182,11 +191,13 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
|
||||
}
|
||||
responses.add(MenuBarMessage::SendLayout);
|
||||
responses.add(NodeGraphMessage::UpdateLayerPanel);
|
||||
responses.add(PropertiesPanelMessage::Refresh);
|
||||
responses.add(NodeGraphMessage::SendSelectedNodes);
|
||||
responses.add(ArtboardToolMessage::UpdateSelectedArtboard);
|
||||
responses.add(DocumentMessage::DocumentStructureChanged);
|
||||
responses.add(OverlaysMessage::Draw);
|
||||
responses.add(NodeGraphMessage::SendGraph);
|
||||
responses.add(PortfolioMessage::SubmitActiveGraphRender);
|
||||
}
|
||||
NodeGraphMessage::CreateWire { output_connector, input_connector } => {
|
||||
// TODO: Add support for flattening NodeInput::Network exports in flatten_with_fns https://github.com/GraphiteEditor/Graphite/issues/1762
|
||||
@@ -424,6 +435,77 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
|
||||
responses.add(DocumentMessage::ZoomCanvasTo100Percent);
|
||||
}
|
||||
}
|
||||
NodeGraphMessage::ExposeEncapsulatingPrimaryInput { exposed } => {
|
||||
let Some((node_id, network_path)) = breadcrumb_network_path.split_last() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let encapsulating_connector = InputConnector::node(*node_id, 0);
|
||||
if !exposed {
|
||||
network_interface.disconnect_input(&encapsulating_connector, network_path);
|
||||
}
|
||||
|
||||
let Some(mut input) = network_interface.input_from_connector(&encapsulating_connector, network_path).cloned() else {
|
||||
return;
|
||||
};
|
||||
|
||||
if let NodeInput::Value { exposed: old_exposed, .. } = &mut input {
|
||||
*old_exposed = exposed;
|
||||
}
|
||||
|
||||
network_interface.set_input(&encapsulating_connector, input, network_path);
|
||||
|
||||
let Some(outward_wires) = network_interface.outward_wires(breadcrumb_network_path) else {
|
||||
log::error!("Could not get outward wires in remove_import");
|
||||
return;
|
||||
};
|
||||
let Some(downstream_connections) = outward_wires.get(&OutputConnector::Import(0)).cloned() else {
|
||||
log::error!("Could not get outward wires for import in remove_import");
|
||||
return;
|
||||
};
|
||||
|
||||
// Disconnect all connections in the encapsulating network
|
||||
for downstream_connection in &downstream_connections {
|
||||
network_interface.disconnect_input(downstream_connection, breadcrumb_network_path);
|
||||
}
|
||||
|
||||
responses.add(NodeGraphMessage::UpdateImportsExports);
|
||||
responses.add(NodeGraphMessage::SendWires);
|
||||
}
|
||||
NodeGraphMessage::ExposePrimaryExport { exposed } => {
|
||||
let export_connector: InputConnector = InputConnector::Export(0);
|
||||
if !exposed {
|
||||
network_interface.disconnect_input(&export_connector, breadcrumb_network_path);
|
||||
}
|
||||
|
||||
let Some(mut input) = network_interface.input_from_connector(&export_connector, breadcrumb_network_path).cloned() else {
|
||||
return;
|
||||
};
|
||||
|
||||
if let NodeInput::Value { exposed: old_exposed, .. } = &mut input {
|
||||
*old_exposed = exposed;
|
||||
}
|
||||
|
||||
network_interface.set_input(&export_connector, input, breadcrumb_network_path);
|
||||
|
||||
// Disconnect all connections in the encapsulating network
|
||||
if let Some((encapsulating_node, encapsulating_path)) = breadcrumb_network_path.split_last() {
|
||||
let Some(outward_wires) = network_interface.outward_wires(encapsulating_path) else {
|
||||
log::error!("Could not get outward wires in remove_import");
|
||||
return;
|
||||
};
|
||||
let Some(downstream_connections) = outward_wires.get(&OutputConnector::node(*encapsulating_node, 0)).cloned() else {
|
||||
log::error!("Could not get outward wires for import in remove_import");
|
||||
return;
|
||||
};
|
||||
|
||||
for downstream_connection in &downstream_connections {
|
||||
network_interface.disconnect_input(downstream_connection, encapsulating_path);
|
||||
}
|
||||
}
|
||||
|
||||
responses.add(NodeGraphMessage::UpdateImportsExports);
|
||||
}
|
||||
NodeGraphMessage::InsertNode { node_id, node_template } => {
|
||||
network_interface.insert_node(node_id, node_template, selection_network_path);
|
||||
}
|
||||
@@ -729,21 +811,21 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
|
||||
return;
|
||||
};
|
||||
|
||||
if modify_import_export.add_import_export.clicked_input_port_from_point(node_graph_point).is_some() {
|
||||
if let Some(remove_import_index) = modify_import_export.remove_imports_exports.clicked_output_port_from_point(node_graph_point) {
|
||||
responses.add(DocumentMessage::AddTransaction);
|
||||
responses.add(NodeGraphMessage::AddExport);
|
||||
return;
|
||||
} else if modify_import_export.add_import_export.clicked_output_port_from_point(node_graph_point).is_some() {
|
||||
responses.add(DocumentMessage::AddTransaction);
|
||||
responses.add(NodeGraphMessage::AddImport);
|
||||
return;
|
||||
} else if let Some(remove_import_index) = modify_import_export.remove_imports_exports.clicked_output_port_from_point(node_graph_point) {
|
||||
responses.add(DocumentMessage::AddTransaction);
|
||||
responses.add(NodeGraphMessage::RemoveImport { import_index: remove_import_index });
|
||||
if remove_import_index == 0 {
|
||||
responses.add(NodeGraphMessage::ExposeEncapsulatingPrimaryInput { exposed: false })
|
||||
} else {
|
||||
responses.add(NodeGraphMessage::RemoveImport { import_index: remove_import_index });
|
||||
}
|
||||
return;
|
||||
} else if let Some(remove_export_index) = modify_import_export.remove_imports_exports.clicked_input_port_from_point(node_graph_point) {
|
||||
responses.add(DocumentMessage::AddTransaction);
|
||||
responses.add(NodeGraphMessage::RemoveExport { export_index: remove_export_index });
|
||||
if remove_export_index == 0 {
|
||||
responses.add(NodeGraphMessage::ExposePrimaryExport { exposed: false })
|
||||
} else {
|
||||
responses.add(NodeGraphMessage::RemoveExport { export_index: remove_export_index });
|
||||
}
|
||||
return;
|
||||
} else if let Some(move_import_index) = modify_import_export.reorder_imports_exports.clicked_output_port_from_point(node_graph_point) {
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
@@ -779,10 +861,8 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
|
||||
}
|
||||
|
||||
// Alt-click sets the clicked node as previewed
|
||||
if alt_click {
|
||||
if let Some(clicked_node) = clicked_id {
|
||||
self.preview_on_mouse_up = Some(clicked_node);
|
||||
}
|
||||
if alt_click && let Some(clicked_node) = clicked_id {
|
||||
self.preview_on_mouse_up = Some(clicked_node);
|
||||
}
|
||||
|
||||
// Begin moving an existing wire
|
||||
@@ -808,14 +888,8 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
|
||||
self.initial_disconnecting = false;
|
||||
|
||||
self.wire_in_progress_from_connector = network_interface.output_position(&clicked_output, selection_network_path);
|
||||
if let Some((output_type, source)) = clicked_output
|
||||
.node_id()
|
||||
.map(|node_id| network_interface.output_type(&node_id, clicked_output.index(), breadcrumb_network_path))
|
||||
{
|
||||
self.wire_in_progress_type = FrontendGraphDataType::displayed_type(&output_type, &source);
|
||||
} else {
|
||||
self.wire_in_progress_type = FrontendGraphDataType::General;
|
||||
}
|
||||
let (output_type, source) = &network_interface.output_type(&clicked_output, breadcrumb_network_path);
|
||||
self.wire_in_progress_type = FrontendGraphDataType::displayed_type(output_type, source);
|
||||
|
||||
self.update_node_graph_hints(responses);
|
||||
return;
|
||||
@@ -984,8 +1058,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
|
||||
to_connector_is_layer,
|
||||
GraphWireStyle::Direct,
|
||||
);
|
||||
let mut path_string = String::new();
|
||||
let _ = vector_wire.subpath_to_svg(&mut path_string, DAffine2::IDENTITY);
|
||||
let path_string = vector_wire.to_svg();
|
||||
let wire_path = WirePath {
|
||||
path_string,
|
||||
data_type: self.wire_in_progress_type,
|
||||
@@ -1047,7 +1120,8 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
|
||||
};
|
||||
(position > point.y).then_some(*index)
|
||||
})
|
||||
.unwrap_or(modify_import_export.reorder_imports_exports.output_ports().count()),
|
||||
.filter(|end_index| *end_index > 0) // An import cannot be reordered to be the primary
|
||||
.unwrap_or_else(|| modify_import_export.reorder_imports_exports.output_ports().count() + 1),
|
||||
);
|
||||
responses.add(FrontendMessage::UpdateImportReorderIndex { index: self.end_index });
|
||||
} else if self.reordering_export.is_some() {
|
||||
@@ -1067,7 +1141,8 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
|
||||
};
|
||||
(position > point.y).then_some(*index)
|
||||
})
|
||||
.unwrap_or(modify_import_export.reorder_imports_exports.input_ports().count()),
|
||||
.filter(|end_index| *end_index > 0) // An export cannot be reordered to be the primary
|
||||
.unwrap_or_else(|| modify_import_export.reorder_imports_exports.input_ports().count() + 1),
|
||||
);
|
||||
responses.add(FrontendMessage::UpdateExportReorderIndex { index: self.end_index });
|
||||
}
|
||||
@@ -1122,27 +1197,27 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
|
||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||
|
||||
responses.add(NodeGraphMessage::SendGraph);
|
||||
} else if output_connector.is_some() && input_connector.is_none() && !self.initial_disconnecting {
|
||||
} else if !self.initial_disconnecting
|
||||
&& input_connector.is_none()
|
||||
&& let Some(output_connector) = output_connector
|
||||
{
|
||||
// If the add node menu is already open, we don't want to open it again
|
||||
if self.context_menu.is_some() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the output types from the network interface
|
||||
let (output_type, type_source) = network_interface.output_type(&output_connector, selection_network_path);
|
||||
let Some(network_metadata) = network_interface.network_metadata(selection_network_path) else {
|
||||
warn!("No network_metadata");
|
||||
return;
|
||||
};
|
||||
// Get the compatible type from the output connector
|
||||
let compatible_type = output_connector.and_then(|output_connector| {
|
||||
output_connector.node_id().and_then(|node_id| {
|
||||
// Get the output types from the network interface
|
||||
let (output_type, type_source) = network_interface.output_type(&node_id, output_connector.index(), selection_network_path);
|
||||
|
||||
match type_source {
|
||||
TypeSource::RandomProtonodeImplementation | TypeSource::Error(_) => None,
|
||||
_ => Some(format!("type:{}", output_type.nested_type())),
|
||||
}
|
||||
})
|
||||
});
|
||||
let compatible_type = match type_source {
|
||||
TypeSource::RandomProtonodeImplementation | TypeSource::Error(_) => None,
|
||||
_ => Some(format!("type:{}", output_type.nested_type())),
|
||||
};
|
||||
|
||||
let appear_right_of_mouse = if ipp.mouse.position.x > ipp.viewport_bounds.size().x - 173. { -173. } else { 0. };
|
||||
let appear_above_mouse = if ipp.mouse.position.y > ipp.viewport_bounds.size().y - 34. { -34. } else { 0. };
|
||||
let node_graph_shift = DVec2::new(appear_right_of_mouse, appear_above_mouse) / network_metadata.persistent_metadata.navigation_metadata.node_graph_to_viewport.matrix2.x_axis.x;
|
||||
@@ -1222,7 +1297,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
|
||||
.filter(|input| input.1.as_value().is_some())
|
||||
.map(|input| input.0);
|
||||
if let Some(selected_node_input_connect_index) = selected_node_input_connect_index {
|
||||
let Some(bounding_box) = network_interface.node_bounding_box(&selected_node_id, selection_network_path) else {
|
||||
let Some(node_bbox) = network_interface.node_bounding_box(&selected_node_id, selection_network_path) else {
|
||||
log::error!("Could not get bounding box for node: {selected_node_id}");
|
||||
return;
|
||||
};
|
||||
@@ -1243,34 +1318,15 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
|
||||
{
|
||||
return None;
|
||||
}
|
||||
log::debug!("preferences.graph_wire_style: {:?}", preferences.graph_wire_style);
|
||||
|
||||
let (wire, is_stack) = network_interface.vector_wire_from_input(&input, preferences.graph_wire_style, selection_network_path)?;
|
||||
|
||||
let bbox_rect = kurbo::Rect::new(bounding_box[0].x, bounding_box[0].y, bounding_box[1].x, bounding_box[1].y);
|
||||
let node_bbox = kurbo::Rect::new(node_bbox[0].x, node_bbox[0].y, node_bbox[1].x, node_bbox[1].y).to_path(DEFAULT_ACCURACY);
|
||||
let inside = bezpath_is_inside_bezpath(&wire, &node_bbox, None, None);
|
||||
|
||||
let p1 = DVec2::new(bbox_rect.x0, bbox_rect.y0);
|
||||
let p2 = DVec2::new(bbox_rect.x1, bbox_rect.y0);
|
||||
let p3 = DVec2::new(bbox_rect.x1, bbox_rect.y1);
|
||||
let p4 = DVec2::new(bbox_rect.x0, bbox_rect.y1);
|
||||
let ps = [p1, p2, p3, p4];
|
||||
|
||||
let inside = wire.is_inside_subpath(&Subpath::from_anchors_linear(ps, true), None, None);
|
||||
|
||||
let wire = subpath_to_kurbo_bezpath(wire);
|
||||
|
||||
let intersect = wire.segments().any(|segment| {
|
||||
let rect = kurbo::Rect::new(bounding_box[0].x, bounding_box[0].y, bounding_box[1].x, bounding_box[1].y);
|
||||
|
||||
let top_line = Line::new(Point::new(rect.x0, rect.y0), Point::new(rect.x1, rect.y0));
|
||||
let bottom_line = Line::new(Point::new(rect.x0, rect.y1), Point::new(rect.x1, rect.y1));
|
||||
let left_line = Line::new(Point::new(rect.x0, rect.y0), Point::new(rect.x0, rect.y1));
|
||||
let right_line = Line::new(Point::new(rect.x1, rect.y0), Point::new(rect.x1, rect.y1));
|
||||
|
||||
!segment.intersect_line(top_line).is_empty()
|
||||
|| !segment.intersect_line(bottom_line).is_empty()
|
||||
|| !segment.intersect_line(left_line).is_empty()
|
||||
|| !segment.intersect_line(right_line).is_empty()
|
||||
});
|
||||
let intersect = wire
|
||||
.segments()
|
||||
.any(|segment| node_bbox.segments().filter_map(|segment| segment.as_line()).any(|line| !segment.intersect_line(line).is_empty()));
|
||||
|
||||
(intersect || inside).then_some((input, is_stack))
|
||||
})
|
||||
@@ -1485,11 +1541,13 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
|
||||
}
|
||||
NodeGraphMessage::RemoveImport { import_index: usize } => {
|
||||
network_interface.remove_import(usize, selection_network_path);
|
||||
responses.add(NodeGraphMessage::UpdateImportsExports);
|
||||
responses.add(NodeGraphMessage::SendGraph);
|
||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||
}
|
||||
NodeGraphMessage::RemoveExport { export_index: usize } => {
|
||||
network_interface.remove_export(usize, selection_network_path);
|
||||
responses.add(NodeGraphMessage::UpdateImportsExports);
|
||||
responses.add(NodeGraphMessage::SendGraph);
|
||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||
}
|
||||
@@ -1515,7 +1573,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
|
||||
return;
|
||||
};
|
||||
selected_nodes.add_selected_nodes(nodes);
|
||||
responses.add(BroadcastEvent::SelectionChanged);
|
||||
responses.add(EventMessage::SelectionChanged);
|
||||
}
|
||||
NodeGraphMessage::SelectedNodesRemove { nodes } => {
|
||||
let Some(selected_nodes) = network_interface.selected_nodes_mut(selection_network_path) else {
|
||||
@@ -1523,7 +1581,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
|
||||
return;
|
||||
};
|
||||
selected_nodes.retain_selected_nodes(|node| !nodes.contains(node));
|
||||
responses.add(BroadcastEvent::SelectionChanged);
|
||||
responses.add(EventMessage::SelectionChanged);
|
||||
}
|
||||
NodeGraphMessage::SelectedNodesSet { nodes } => {
|
||||
let Some(selected_nodes) = network_interface.selected_nodes_mut(selection_network_path) else {
|
||||
@@ -1531,8 +1589,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
|
||||
return;
|
||||
};
|
||||
selected_nodes.set_selected_nodes(nodes);
|
||||
responses.add(BroadcastEvent::SelectionChanged);
|
||||
responses.add(PropertiesPanelMessage::Refresh);
|
||||
responses.add(EventMessage::SelectionChanged);
|
||||
}
|
||||
NodeGraphMessage::SendClickTargets => responses.add(FrontendMessage::UpdateClickTargets {
|
||||
click_targets: Some(network_interface.collect_frontend_click_targets(breadcrumb_network_path)),
|
||||
@@ -1560,7 +1617,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
|
||||
let mut nodes = Vec::new();
|
||||
for node_id in &self.frontend_nodes {
|
||||
let Some(node_bbox) = network_interface.node_bounding_box(node_id, breadcrumb_network_path) else {
|
||||
log::error!("Could not get bbox for node: {:?}", node_id);
|
||||
log::error!("Could not get bbox for node: {node_id:?}");
|
||||
continue;
|
||||
};
|
||||
|
||||
@@ -1724,6 +1781,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
|
||||
responses.add(DocumentMessage::RenderRulers);
|
||||
responses.add(DocumentMessage::RenderScrollbars);
|
||||
responses.add(NodeGraphMessage::SendGraph);
|
||||
responses.add(OverlaysMessage::Draw); // Redraw overlays to update artboard names
|
||||
}
|
||||
NodeGraphMessage::SetDisplayNameImpl { node_id, alias } => {
|
||||
network_interface.set_display_name(&node_id, alias, selection_network_path);
|
||||
@@ -1764,7 +1822,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
|
||||
}
|
||||
NodeGraphMessage::ToggleLocked { node_id } => {
|
||||
let Some(node_metadata) = network_interface.document_network_metadata().persistent_metadata.node_metadata.get(&node_id) else {
|
||||
log::error!("Cannot get node {:?} in NodeGraphMessage::ToggleLocked", node_id);
|
||||
log::error!("Cannot get node {node_id:?} in NodeGraphMessage::ToggleLocked");
|
||||
return;
|
||||
};
|
||||
|
||||
@@ -1864,7 +1922,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
|
||||
|
||||
let shift = ipp.keyboard.get(Key::Shift as usize);
|
||||
let Some(selected_nodes) = network_interface.selected_nodes_in_nested_network(selection_network_path) else {
|
||||
log::error!("Could not get selected nodes in PointerMove");
|
||||
log::error!("Could not get selected nodes in UpdateBoxSelection");
|
||||
return;
|
||||
};
|
||||
let previous_selection = selected_nodes.selected_nodes_ref().iter().cloned().collect::<HashSet<_>>();
|
||||
@@ -1880,7 +1938,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
|
||||
continue;
|
||||
};
|
||||
let quad = Quad::from_box([box_selection_start, box_selection_end_graph]);
|
||||
if click_targets.node_click_target.intersect_path(|| quad.bezier_lines(), DAffine2::IDENTITY) {
|
||||
if click_targets.node_click_target.intersect_path(|| quad.to_lines(), DAffine2::IDENTITY) {
|
||||
nodes.insert(node_id);
|
||||
}
|
||||
}
|
||||
@@ -1893,34 +1951,30 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
|
||||
}
|
||||
}
|
||||
NodeGraphMessage::UpdateImportsExports => {
|
||||
let imports = network_interface.frontend_imports(breadcrumb_network_path).unwrap_or_default();
|
||||
let exports = network_interface.frontend_exports(breadcrumb_network_path).unwrap_or_default();
|
||||
let add_import = network_interface
|
||||
.frontend_import_export_modify(
|
||||
|modify_import_export_click_target| modify_import_export_click_target.add_import_export.output_ports().collect::<Vec<_>>(),
|
||||
breadcrumb_network_path,
|
||||
)
|
||||
.into_iter()
|
||||
.next();
|
||||
let add_export = network_interface
|
||||
.frontend_import_export_modify(
|
||||
|modify_import_export_click_target| modify_import_export_click_target.add_import_export.input_ports().collect::<Vec<_>>(),
|
||||
breadcrumb_network_path,
|
||||
)
|
||||
.into_iter()
|
||||
.next();
|
||||
let imports = network_interface.frontend_imports(breadcrumb_network_path);
|
||||
let exports = network_interface.frontend_exports(breadcrumb_network_path);
|
||||
|
||||
let Some((import_position, export_position)) = network_interface.import_export_position(breadcrumb_network_path) else {
|
||||
log::error!("Could not get import export positions");
|
||||
return;
|
||||
};
|
||||
|
||||
// Do not show the add import or add export button in the document network;
|
||||
let add_import_export = !breadcrumb_network_path.is_empty();
|
||||
|
||||
responses.add(NodeGraphMessage::UpdateVisibleNodes);
|
||||
responses.add(NodeGraphMessage::SendWires);
|
||||
responses.add(FrontendMessage::UpdateImportsExports {
|
||||
imports,
|
||||
exports,
|
||||
add_import,
|
||||
add_export,
|
||||
import_position,
|
||||
export_position,
|
||||
add_import_export,
|
||||
});
|
||||
}
|
||||
|
||||
NodeGraphMessage::UpdateLayerPanel => {
|
||||
Self::update_layer_panel(network_interface, selection_network_path, collapsed, responses);
|
||||
Self::update_layer_panel(network_interface, selection_network_path, collapsed, layers_panel_open, responses);
|
||||
}
|
||||
NodeGraphMessage::UpdateEdges => {
|
||||
// Update the import/export UI edges whenever the PTZ changes or the bounding box of all nodes changes
|
||||
@@ -1931,7 +1985,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
|
||||
return;
|
||||
};
|
||||
selected_nodes.clear_selected_nodes();
|
||||
responses.add(BroadcastEvent::SelectionChanged);
|
||||
responses.add(EventMessage::SelectionChanged);
|
||||
|
||||
responses.add(NodeGraphMessage::SendGraph);
|
||||
}
|
||||
@@ -2033,11 +2087,6 @@ impl NodeGraphMessageHandler {
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(network) = network_interface.nested_network(breadcrumb_network_path) else {
|
||||
warn!("No network in update_selection_action_buttons");
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(selected_nodes) = network_interface.selected_nodes_in_nested_network(breadcrumb_network_path) else {
|
||||
warn!("No selected nodes in update_selection_action_buttons");
|
||||
return;
|
||||
@@ -2051,6 +2100,7 @@ impl NodeGraphMessageHandler {
|
||||
let mut selected_layers = selected_nodes.selected_layers(network_interface.document_metadata());
|
||||
let selected_layer = selected_layers.next();
|
||||
let has_multiple_selection = selected_layers.next().is_some();
|
||||
for _ in selected_layers {}
|
||||
|
||||
let mut widgets = vec![
|
||||
PopoverButton::new()
|
||||
@@ -2063,7 +2113,7 @@ impl NodeGraphMessageHandler {
|
||||
let graph_layer = graph_modification_utils::NodeGraphLayer::new(layer, network_interface);
|
||||
let node_type = graph_layer.horizontal_layer_flow().nth(1);
|
||||
if let Some(node_id) = node_type {
|
||||
let (output_type, _) = network_interface.output_type(&node_id, 0, &[]);
|
||||
let (output_type, _) = network_interface.output_type(&OutputConnector::node(node_id, 0), &[]);
|
||||
Some(format!("type:{}", output_type.nested_type()))
|
||||
} else {
|
||||
None
|
||||
@@ -2149,6 +2199,11 @@ impl NodeGraphMessageHandler {
|
||||
|
||||
let mut selection = selected_nodes.selected_nodes();
|
||||
let (selection, no_other_selections) = (selection.next(), selection.count() == 0);
|
||||
|
||||
let Some(network) = network_interface.nested_network(breadcrumb_network_path) else {
|
||||
warn!("No network in update_selection_action_buttons");
|
||||
return;
|
||||
};
|
||||
let previewing = if matches!(network_interface.previewing(breadcrumb_network_path), Previewing::Yes { .. }) {
|
||||
network.exports.iter().find_map(|export| {
|
||||
let NodeInput::Node { node_id, .. } = export else { return None };
|
||||
@@ -2265,6 +2320,12 @@ impl NodeGraphMessageHandler {
|
||||
}
|
||||
}
|
||||
|
||||
// The same layer/node may appear several times. Sort and dedup them for a stable ordering.
|
||||
layers.sort();
|
||||
layers.dedup();
|
||||
nodes.sort();
|
||||
nodes.dedup();
|
||||
|
||||
// Next, we decide what to display based on the number of layers and nodes selected
|
||||
match *layers.as_slice() {
|
||||
// If no layers are selected, show properties for all selected nodes
|
||||
@@ -2370,12 +2431,12 @@ impl NodeGraphMessageHandler {
|
||||
.icon(Some("Node".to_string()))
|
||||
.tooltip("Add an operation to the end of this layer's chain of nodes")
|
||||
.popover_layout({
|
||||
let layer_identifier = LayerNodeIdentifier::new(layer, &context.network_interface);
|
||||
let layer_identifier = LayerNodeIdentifier::new(layer, context.network_interface);
|
||||
let compatible_type = {
|
||||
let graph_layer = graph_modification_utils::NodeGraphLayer::new(layer_identifier, &context.network_interface);
|
||||
let graph_layer = graph_modification_utils::NodeGraphLayer::new(layer_identifier, context.network_interface);
|
||||
let node_type = graph_layer.horizontal_layer_flow().nth(1);
|
||||
if let Some(node_id) = node_type {
|
||||
let (output_type, _) = context.network_interface.output_type(&node_id, 0, &[]);
|
||||
let (output_type, _) = context.network_interface.output_type(&OutputConnector::node(node_id, 0), &[]);
|
||||
Some(format!("type:{}", output_type.nested_type()))
|
||||
} else {
|
||||
None
|
||||
@@ -2454,7 +2515,7 @@ impl NodeGraphMessageHandler {
|
||||
} else {
|
||||
added_wires.push(WirePathUpdate {
|
||||
id: NodeId(u64::MAX),
|
||||
input_index: usize::MAX,
|
||||
input_index: u32::MAX as usize,
|
||||
wire_path_update: None,
|
||||
})
|
||||
}
|
||||
@@ -2463,110 +2524,48 @@ impl NodeGraphMessageHandler {
|
||||
}
|
||||
|
||||
fn collect_nodes(&self, network_interface: &mut NodeNetworkInterface, breadcrumb_network_path: &[NodeId]) -> Vec<FrontendNode> {
|
||||
let Some(outward_wires) = network_interface.outward_wires(breadcrumb_network_path).cloned() else {
|
||||
return Vec::new();
|
||||
};
|
||||
let mut can_be_layer_lookup = HashSet::new();
|
||||
let mut position_lookup = HashMap::new();
|
||||
let Some(network) = network_interface.nested_network(breadcrumb_network_path) else {
|
||||
log::error!("Could not get nested network when collecting nodes");
|
||||
return Vec::new();
|
||||
};
|
||||
|
||||
for node_id in network.nodes.keys().cloned().collect::<Vec<_>>() {
|
||||
if network_interface.is_eligible_to_be_layer(&node_id, breadcrumb_network_path) {
|
||||
can_be_layer_lookup.insert(node_id);
|
||||
}
|
||||
if let Some(position) = network_interface.position(&node_id, breadcrumb_network_path) {
|
||||
position_lookup.insert(node_id, position);
|
||||
} else {
|
||||
log::error!("Could not get position for node {node_id}");
|
||||
}
|
||||
}
|
||||
|
||||
let mut frontend_inputs_lookup = frontend_inputs_lookup(breadcrumb_network_path, network_interface);
|
||||
let Some(network) = network_interface.nested_network(breadcrumb_network_path) else {
|
||||
log::error!("Could not get nested network when collecting nodes");
|
||||
return Vec::new();
|
||||
};
|
||||
let Some(network_metadata) = network_interface.network_metadata(breadcrumb_network_path) else {
|
||||
log::error!("Could not get network_metadata when collecting nodes");
|
||||
return Vec::new();
|
||||
};
|
||||
|
||||
let mut nodes = Vec::new();
|
||||
for (&node_id, node) in &network.nodes {
|
||||
let node_id_path = [breadcrumb_network_path, (&[node_id])].concat();
|
||||
for (node_id, visible) in network.nodes.iter().map(|(node_id, node)| (*node_id, node.visible)).collect::<Vec<_>>() {
|
||||
let node_id_path = [breadcrumb_network_path, &[node_id]].concat();
|
||||
|
||||
let inputs = frontend_inputs_lookup.remove(&node_id).unwrap_or_default();
|
||||
let primary_input_connector = InputConnector::node(node_id, 0);
|
||||
|
||||
let mut inputs = inputs.into_iter().map(|input| {
|
||||
input.map(|input| FrontendGraphInput {
|
||||
data_type: FrontendGraphDataType::displayed_type(&input.ty, &input.type_source),
|
||||
resolved_type: format!("{:?}", &input.ty),
|
||||
valid_types: input.valid_types.iter().map(|ty| ty.to_string()).collect(),
|
||||
name: input.input_name,
|
||||
description: input.input_description,
|
||||
connected_to: input.output_connector,
|
||||
})
|
||||
});
|
||||
|
||||
let primary_input = inputs.next().flatten();
|
||||
let exposed_inputs = inputs.flatten().collect();
|
||||
|
||||
let (output_type, type_source) = network_interface.output_type(&node_id, 0, breadcrumb_network_path);
|
||||
let frontend_data_type = FrontendGraphDataType::displayed_type(&output_type, &type_source);
|
||||
|
||||
let connected_to = outward_wires.get(&OutputConnector::node(node_id, 0)).cloned().unwrap_or_default();
|
||||
let primary_output = if network_interface.has_primary_output(&node_id, breadcrumb_network_path) {
|
||||
Some(FrontendGraphOutput {
|
||||
data_type: frontend_data_type,
|
||||
name: "Output 1".to_string(),
|
||||
description: String::new(),
|
||||
resolved_type: format!("{:?}", output_type),
|
||||
connected_to,
|
||||
})
|
||||
let primary_input = if network_interface
|
||||
.input_from_connector(&primary_input_connector, breadcrumb_network_path)
|
||||
.is_some_and(|input| input.is_exposed())
|
||||
{
|
||||
network_interface.frontend_input_from_connector(&primary_input_connector, breadcrumb_network_path)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let exposed_inputs = (1..network_interface.number_of_inputs(&node_id, breadcrumb_network_path))
|
||||
.filter_map(|input_index| network_interface.frontend_input_from_connector(&InputConnector::node(node_id, input_index), breadcrumb_network_path))
|
||||
.collect();
|
||||
|
||||
let mut exposed_outputs = Vec::new();
|
||||
for output_index in 0..network_interface.number_of_outputs(&node_id, breadcrumb_network_path) {
|
||||
if output_index == 0 && network_interface.has_primary_output(&node_id, breadcrumb_network_path) {
|
||||
continue;
|
||||
}
|
||||
let (output_type, type_source) = network_interface.output_type(&node_id, 0, breadcrumb_network_path);
|
||||
let data_type = FrontendGraphDataType::displayed_type(&output_type, &type_source);
|
||||
let primary_output = network_interface.frontend_output_from_connector(&OutputConnector::node(node_id, 0), breadcrumb_network_path);
|
||||
|
||||
let Some(node_metadata) = network_metadata.persistent_metadata.node_metadata.get(&node_id) else {
|
||||
log::error!("Could not get node_metadata when getting output for {node_id}");
|
||||
continue;
|
||||
};
|
||||
let output_name = node_metadata
|
||||
.persistent_metadata
|
||||
.output_names
|
||||
.get(output_index)
|
||||
.cloned()
|
||||
.filter(|output_name| !output_name.is_empty())
|
||||
.unwrap_or_else(|| output_type.nested_type().to_string());
|
||||
|
||||
let connected_to = outward_wires.get(&OutputConnector::node(node_id, output_index)).cloned().unwrap_or_default();
|
||||
exposed_outputs.push(FrontendGraphOutput {
|
||||
data_type,
|
||||
name: output_name,
|
||||
description: String::new(),
|
||||
resolved_type: format!("{:?}", output_type),
|
||||
connected_to,
|
||||
});
|
||||
}
|
||||
let Some(network) = network_interface.nested_network(breadcrumb_network_path) else {
|
||||
log::error!("Could not get nested network when collecting nodes");
|
||||
return Vec::new();
|
||||
let exposed_outputs = (1..network_interface.number_of_outputs(&node_id, breadcrumb_network_path))
|
||||
.filter_map(|output_index| network_interface.frontend_output_from_connector(&OutputConnector::node(node_id, output_index), breadcrumb_network_path))
|
||||
.collect();
|
||||
let (primary_output_connected_to_layer, primary_input_connected_to_layer) = if network_interface.is_layer(&node_id, breadcrumb_network_path) {
|
||||
(
|
||||
network_interface.primary_output_connected_to_layer(&node_id, breadcrumb_network_path),
|
||||
network_interface.primary_input_connected_to_layer(&node_id, breadcrumb_network_path),
|
||||
)
|
||||
} else {
|
||||
(false, false)
|
||||
};
|
||||
let is_export = network.exports.first().is_some_and(|export| export.as_node().is_some_and(|export_node_id| node_id == export_node_id));
|
||||
|
||||
let is_export = network_interface
|
||||
.input_from_connector(&InputConnector::Export(0), breadcrumb_network_path)
|
||||
.is_some_and(|export| export.as_node().is_some_and(|export_node_id| node_id == export_node_id));
|
||||
let is_root_node = network_interface.root_node(breadcrumb_network_path).is_some_and(|root_node| root_node.node_id == node_id);
|
||||
|
||||
let Some(position) = position_lookup.get(&node_id).map(|pos| (pos.x, pos.y)) else {
|
||||
let Some(position) = network_interface.position(&node_id, breadcrumb_network_path) else {
|
||||
log::error!("Could not get position for node: {node_id}");
|
||||
continue;
|
||||
};
|
||||
@@ -2592,19 +2591,20 @@ impl NodeGraphMessageHandler {
|
||||
is_layer: network_interface
|
||||
.node_metadata(&node_id, breadcrumb_network_path)
|
||||
.is_some_and(|node_metadata| node_metadata.persistent_metadata.is_layer()),
|
||||
can_be_layer: can_be_layer_lookup.contains(&node_id),
|
||||
can_be_layer: network_interface.is_eligible_to_be_layer(&node_id, breadcrumb_network_path),
|
||||
reference: network_interface.reference(&node_id, breadcrumb_network_path).cloned().unwrap_or_default(),
|
||||
display_name: network_interface.display_name(&node_id, breadcrumb_network_path),
|
||||
primary_input,
|
||||
exposed_inputs,
|
||||
primary_output,
|
||||
exposed_outputs,
|
||||
primary_output_connected_to_layer,
|
||||
primary_input_connected_to_layer,
|
||||
position,
|
||||
previewed,
|
||||
visible: node.visible,
|
||||
visible,
|
||||
locked,
|
||||
errors,
|
||||
ui_only: false,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -2626,7 +2626,11 @@ impl NodeGraphMessageHandler {
|
||||
Some(subgraph_names)
|
||||
}
|
||||
|
||||
fn update_layer_panel(network_interface: &NodeNetworkInterface, selection_network_path: &[NodeId], collapsed: &CollapsedLayers, responses: &mut VecDeque<Message>) {
|
||||
fn update_layer_panel(network_interface: &NodeNetworkInterface, selection_network_path: &[NodeId], collapsed: &CollapsedLayers, layers_panel_open: bool, responses: &mut VecDeque<Message>) {
|
||||
if !layers_panel_open {
|
||||
return;
|
||||
}
|
||||
|
||||
let selected_layers = network_interface
|
||||
.selected_nodes()
|
||||
.selected_layers(network_interface.document_metadata())
|
||||
@@ -2709,7 +2713,7 @@ impl NodeGraphMessageHandler {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_node_graph_hints(&self, responses: &mut VecDeque<Message>) {
|
||||
fn update_node_graph_hints(&self, responses: &mut VecDeque<Message>) {
|
||||
// A wire is in progress and its start and end connectors are set
|
||||
let wiring = self.wire_in_progress_from_connector.is_some();
|
||||
|
||||
@@ -2750,73 +2754,6 @@ impl NodeGraphMessageHandler {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct InputLookup {
|
||||
input_name: String,
|
||||
input_description: String,
|
||||
ty: Type,
|
||||
type_source: TypeSource,
|
||||
valid_types: Vec<Type>,
|
||||
output_connector: Option<OutputConnector>,
|
||||
}
|
||||
|
||||
type FrontendInputsLookup = HashMap<NodeId, Vec<Option<InputLookup>>>;
|
||||
|
||||
/// Create a lookup hashmap that can be used to create the frontend inputs. This is needed because `input_type` requires a mutable `network_interface`.
|
||||
fn frontend_inputs_lookup(breadcrumb_network_path: &[NodeId], network_interface: &mut NodeNetworkInterface) -> FrontendInputsLookup {
|
||||
let Some(network) = network_interface.nested_network(breadcrumb_network_path) else {
|
||||
return Default::default();
|
||||
};
|
||||
let mut frontend_inputs_lookup = HashMap::new();
|
||||
for (node_id, index, output_connector, is_exposed) in network
|
||||
.nodes
|
||||
.iter()
|
||||
.flat_map(|(node_id, node)| {
|
||||
node.inputs
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(index, input)| (*node_id, index, OutputConnector::from_input(input), input.is_exposed()))
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
{
|
||||
// Skip not exposed inputs (they still get an entry to help with finding the primary input)
|
||||
let lookup = if !is_exposed {
|
||||
None
|
||||
} else {
|
||||
// Get the name from the metadata here (since it also requires a reference to the `network_interface`)
|
||||
let (input_name, input_description) = network_interface.displayed_input_name_and_description(&node_id, index, breadcrumb_network_path);
|
||||
Some(InputLookup {
|
||||
input_name,
|
||||
input_description,
|
||||
output_connector,
|
||||
..Default::default()
|
||||
})
|
||||
};
|
||||
frontend_inputs_lookup.entry(node_id).or_insert_with(Vec::new).push(lookup);
|
||||
}
|
||||
|
||||
for (&node_id, value) in frontend_inputs_lookup.iter_mut() {
|
||||
for (index, value) in value.iter_mut().enumerate() {
|
||||
// Skip not exposed inputs for efficiency
|
||||
let Some(value) = value else { continue };
|
||||
// Resolve the type (done in a separate loop because it requires a mutable reference to the `network_interface`)
|
||||
let (ty, type_source) = network_interface.input_type(&InputConnector::node(node_id, index), breadcrumb_network_path);
|
||||
value.ty = ty;
|
||||
value.type_source = type_source;
|
||||
}
|
||||
}
|
||||
|
||||
for (&node_id, value) in frontend_inputs_lookup.iter_mut() {
|
||||
for (index, value) in value.iter_mut().enumerate() {
|
||||
// Skip not exposed inputs for efficiency
|
||||
let Some(value) = value else { continue };
|
||||
// Resolve the type (done in a separate loop because it requires a mutable reference to the `network_interface`)
|
||||
value.valid_types = network_interface.valid_input_types(&InputConnector::node(node_id, index), breadcrumb_network_path);
|
||||
}
|
||||
}
|
||||
frontend_inputs_lookup
|
||||
}
|
||||
|
||||
impl Default for NodeGraphMessageHandler {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
|
||||
@@ -11,6 +11,7 @@ use glam::{DAffine2, DVec2};
|
||||
use graph_craft::Type;
|
||||
use graph_craft::document::value::TaggedValue;
|
||||
use graph_craft::document::{DocumentNode, DocumentNodeImplementation, NodeId, NodeInput};
|
||||
use graphene_std::NodeInputDecleration;
|
||||
use graphene_std::animation::RealTimeMode;
|
||||
use graphene_std::extract_xy::XY;
|
||||
use graphene_std::path_bool::BooleanOperation;
|
||||
@@ -19,16 +20,11 @@ use graphene_std::raster::{
|
||||
BlendMode, CellularDistanceFunction, CellularReturnType, Color, DomainWarpType, FractalType, LuminanceCalculation, NoiseType, RedGreenBlue, RedGreenBlueAlpha, RelativeAbsolute,
|
||||
SelectiveColorChoice,
|
||||
};
|
||||
use graphene_std::raster_types::{CPU, GPU, RasterDataTable};
|
||||
use graphene_std::text::Font;
|
||||
use graphene_std::table::{Table, TableRow};
|
||||
use graphene_std::text::{Font, TextAlign};
|
||||
use graphene_std::transform::{Footprint, ReferencePoint, Transform};
|
||||
use graphene_std::vector::VectorDataTable;
|
||||
use graphene_std::vector::misc::GridType;
|
||||
use graphene_std::vector::misc::{ArcType, MergeByDistanceAlgorithm};
|
||||
use graphene_std::vector::misc::{CentroidType, PointSpacingType};
|
||||
use graphene_std::vector::style::{Fill, FillChoice, FillType, GradientStops};
|
||||
use graphene_std::vector::style::{GradientType, PaintOrder, StrokeAlign, StrokeCap, StrokeJoin};
|
||||
use graphene_std::{GraphicGroupTable, NodeInputDecleration};
|
||||
use graphene_std::vector::misc::{ArcType, CentroidType, GridType, MergeByDistanceAlgorithm, PointSpacingType};
|
||||
use graphene_std::vector::style::{Fill, FillChoice, FillType, GradientStops, GradientType, PaintOrder, StrokeAlign, StrokeCap, StrokeJoin};
|
||||
|
||||
pub(crate) fn string_properties(text: &str) -> Vec<LayoutGroup> {
|
||||
let widget = TextLabel::new(text).widget_holder();
|
||||
@@ -151,16 +147,16 @@ pub(crate) fn property_from_type(
|
||||
Type::Concrete(concrete_type) => {
|
||||
match concrete_type.alias.as_ref().map(|x| x.as_ref()) {
|
||||
// Aliased types (ambiguous values)
|
||||
Some("Percentage") => number_widget(default_info, number_input.percentage().min(min(0.)).max(max(100.))).into(),
|
||||
Some("SignedPercentage") => number_widget(default_info, number_input.percentage().min(min(-100.)).max(max(100.))).into(),
|
||||
Some("Angle") => number_widget(default_info, number_input.mode_range().min(min(-180.)).max(max(180.)).unit(unit.unwrap_or("°"))).into(),
|
||||
Some("Percentage") | Some("PercentageF32") => number_widget(default_info, number_input.percentage().min(min(0.)).max(max(100.))).into(),
|
||||
Some("SignedPercentage") | Some("SignedPercentageF32") => number_widget(default_info, number_input.percentage().min(min(-100.)).max(max(100.))).into(),
|
||||
Some("Angle") | Some("AngleF32") => number_widget(default_info, number_input.mode_range().min(min(-180.)).max(max(180.)).unit(unit.unwrap_or("°"))).into(),
|
||||
Some("Multiplier") => number_widget(default_info, number_input.unit(unit.unwrap_or("x"))).into(),
|
||||
Some("PixelLength") => number_widget(default_info, number_input.min(min(0.)).unit(unit.unwrap_or(" px"))).into(),
|
||||
Some("Length") => number_widget(default_info, number_input.min(min(0.))).into(),
|
||||
Some("Fraction") => number_widget(default_info, number_input.mode_range().min(min(0.)).max(max(1.))).into(),
|
||||
Some("IntegerCount") => number_widget(default_info, number_input.int().min(min(1.))).into(),
|
||||
Some("SeedValue") => number_widget(default_info, number_input.int().min(min(0.))).into(),
|
||||
Some("PixelSize") => coordinate_widget(default_info, "X", "Y", unit.unwrap_or(" px"), None, false),
|
||||
Some("PixelSize") => vec2_widget(default_info, "X", "Y", unit.unwrap_or(" px"), None, false),
|
||||
Some("TextArea") => text_area_widget(default_info).into(),
|
||||
|
||||
// For all other types, use TypeId-based matching
|
||||
@@ -170,29 +166,25 @@ pub(crate) fn property_from_type(
|
||||
// ===============
|
||||
// PRIMITIVE TYPES
|
||||
// ===============
|
||||
Some(x) if x == TypeId::of::<f64>() => number_widget(default_info, number_input.min(min(f64::NEG_INFINITY)).max(max(f64::INFINITY))).into(),
|
||||
Some(x) if x == TypeId::of::<f64>() || x == TypeId::of::<f32>() => number_widget(default_info, number_input.min(min(f64::NEG_INFINITY)).max(max(f64::INFINITY))).into(),
|
||||
Some(x) if x == TypeId::of::<u32>() => number_widget(default_info, number_input.int().min(min(0.)).max(max(f64::from(u32::MAX)))).into(),
|
||||
Some(x) if x == TypeId::of::<u64>() => number_widget(default_info, number_input.int().min(min(0.))).into(),
|
||||
Some(x) if x == TypeId::of::<bool>() => bool_widget(default_info, CheckboxInput::default()).into(),
|
||||
Some(x) if x == TypeId::of::<String>() => text_widget(default_info).into(),
|
||||
Some(x) if x == TypeId::of::<DVec2>() => coordinate_widget(default_info, "X", "Y", "", None, false),
|
||||
Some(x) if x == TypeId::of::<DVec2>() => vec2_widget(default_info, "X", "Y", "", None, false),
|
||||
Some(x) if x == TypeId::of::<DAffine2>() => transform_widget(default_info, &mut extra_widgets),
|
||||
Some(x) if x == TypeId::of::<Color>() => color_widget(default_info, ColorInput::default()),
|
||||
Some(x) if x == TypeId::of::<Option<Color>>() => color_widget(default_info, ColorInput::default()),
|
||||
// ==========================
|
||||
// PRIMITIVE COLLECTION TYPES
|
||||
// ==========================
|
||||
Some(x) if x == TypeId::of::<Vec<f64>>() => array_of_number_widget(default_info, TextInput::default()).into(),
|
||||
Some(x) if x == TypeId::of::<Vec<DVec2>>() => array_of_coordinates_widget(default_info, TextInput::default()).into(),
|
||||
// ====================
|
||||
// GRAPHICAL DATA TYPES
|
||||
// ====================
|
||||
Some(x) if x == TypeId::of::<VectorDataTable>() => vector_data_widget(default_info).into(),
|
||||
Some(x) if x == TypeId::of::<RasterDataTable<CPU>>() || x == TypeId::of::<RasterDataTable<GPU>>() => raster_widget(default_info).into(),
|
||||
Some(x) if x == TypeId::of::<GraphicGroupTable>() => group_widget(default_info).into(),
|
||||
Some(x) if x == TypeId::of::<Vec<DVec2>>() => array_of_vec2_widget(default_info, TextInput::default()).into(),
|
||||
// ============
|
||||
// STRUCT TYPES
|
||||
// ============
|
||||
Some(x) if x == TypeId::of::<Color>() => color_widget(default_info, ColorInput::default().allow_none(false)),
|
||||
Some(x) if x == TypeId::of::<Option<Color>>() => color_widget(default_info, ColorInput::default().allow_none(true)),
|
||||
Some(x) if x == TypeId::of::<Table<Color>>() => color_widget(default_info, ColorInput::default().allow_none(true)),
|
||||
Some(x) if x == TypeId::of::<Table<GradientStops>>() => color_widget(default_info, ColorInput::default().allow_none(false)),
|
||||
Some(x) if x == TypeId::of::<GradientStops>() => color_widget(default_info, ColorInput::default().allow_none(false)),
|
||||
Some(x) if x == TypeId::of::<Font>() => font_widget(default_info),
|
||||
Some(x) if x == TypeId::of::<Curve>() => curve_widget(default_info),
|
||||
@@ -223,6 +215,7 @@ pub(crate) fn property_from_type(
|
||||
Some(x) if x == TypeId::of::<StrokeAlign>() => enum_choice::<StrokeAlign>().for_socket(default_info).property_row(),
|
||||
Some(x) if x == TypeId::of::<PaintOrder>() => enum_choice::<PaintOrder>().for_socket(default_info).property_row(),
|
||||
Some(x) if x == TypeId::of::<ArcType>() => enum_choice::<ArcType>().for_socket(default_info).property_row(),
|
||||
Some(x) if x == TypeId::of::<TextAlign>() => enum_choice::<TextAlign>().for_socket(default_info).property_row(),
|
||||
Some(x) if x == TypeId::of::<MergeByDistanceAlgorithm>() => enum_choice::<MergeByDistanceAlgorithm>().for_socket(default_info).property_row(),
|
||||
Some(x) if x == TypeId::of::<PointSpacingType>() => enum_choice::<PointSpacingType>().for_socket(default_info).property_row(),
|
||||
Some(x) if x == TypeId::of::<BooleanOperation>() => enum_choice::<BooleanOperation>().for_socket(default_info).property_row(),
|
||||
@@ -479,6 +472,10 @@ pub fn footprint_widget(parameter_widgets_info: ParameterWidgetsInfo, extra_widg
|
||||
resolution_widgets.push(
|
||||
NumberInput::new(Some((footprint.resolution.as_dvec2() / bounds).x * 100.))
|
||||
.label("Resolution")
|
||||
.mode_range()
|
||||
.min(0.)
|
||||
.range_min(Some(1.))
|
||||
.range_max(Some(100.))
|
||||
.unit("%")
|
||||
.on_update(update_value(
|
||||
move |x: &NumberInput| {
|
||||
@@ -625,7 +622,7 @@ pub fn transform_widget(parameter_widgets_info: ParameterWidgetsInfo, extra_widg
|
||||
}
|
||||
}
|
||||
|
||||
pub fn coordinate_widget(parameter_widgets_info: ParameterWidgetsInfo, x: &str, y: &str, unit: &str, min: Option<f64>, is_integer: bool) -> LayoutGroup {
|
||||
pub fn vec2_widget(parameter_widgets_info: ParameterWidgetsInfo, x: &str, y: &str, unit: &str, min: Option<f64>, is_integer: bool) -> LayoutGroup {
|
||||
let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info;
|
||||
|
||||
let mut widgets = start_widgets(parameter_widgets_info);
|
||||
@@ -722,7 +719,7 @@ pub fn array_of_number_widget(parameter_widgets_info: ParameterWidgetsInfo, text
|
||||
widgets
|
||||
}
|
||||
|
||||
pub fn array_of_coordinates_widget(parameter_widgets_info: ParameterWidgetsInfo, text_props: TextInput) -> Vec<WidgetHolder> {
|
||||
pub fn array_of_vec2_widget(parameter_widgets_info: ParameterWidgetsInfo, text_props: TextInput) -> Vec<WidgetHolder> {
|
||||
let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info;
|
||||
|
||||
let mut widgets = start_widgets(parameter_widgets_info);
|
||||
@@ -791,33 +788,6 @@ pub fn font_inputs(parameter_widgets_info: ParameterWidgetsInfo) -> (Vec<WidgetH
|
||||
(first_widgets, second_widgets)
|
||||
}
|
||||
|
||||
pub fn vector_data_widget(parameter_widgets_info: ParameterWidgetsInfo) -> Vec<WidgetHolder> {
|
||||
let mut widgets = start_widgets(parameter_widgets_info);
|
||||
|
||||
widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder());
|
||||
widgets.push(TextLabel::new("Vector data is supplied through the node graph").widget_holder());
|
||||
|
||||
widgets
|
||||
}
|
||||
|
||||
pub fn raster_widget(parameter_widgets_info: ParameterWidgetsInfo) -> Vec<WidgetHolder> {
|
||||
let mut widgets = start_widgets(parameter_widgets_info);
|
||||
|
||||
widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder());
|
||||
widgets.push(TextLabel::new("Raster data is supplied through the node graph").widget_holder());
|
||||
|
||||
widgets
|
||||
}
|
||||
|
||||
pub fn group_widget(parameter_widgets_info: ParameterWidgetsInfo) -> Vec<WidgetHolder> {
|
||||
let mut widgets = start_widgets(parameter_widgets_info);
|
||||
|
||||
widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder());
|
||||
widgets.push(TextLabel::new("Group data is supplied through the node graph").widget_holder());
|
||||
|
||||
widgets
|
||||
}
|
||||
|
||||
pub fn number_widget(parameter_widgets_info: ParameterWidgetsInfo, number_props: NumberInput) -> Vec<WidgetHolder> {
|
||||
let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info;
|
||||
|
||||
@@ -837,6 +807,14 @@ pub fn number_widget(parameter_widgets_info: ParameterWidgetsInfo, number_props:
|
||||
.on_commit(commit_value)
|
||||
.widget_holder(),
|
||||
]),
|
||||
Some(&TaggedValue::F32(x)) => widgets.extend_from_slice(&[
|
||||
Separator::new(SeparatorType::Unrelated).widget_holder(),
|
||||
number_props
|
||||
.value(Some(x as f64))
|
||||
.on_update(update_value(move |x: &NumberInput| TaggedValue::F32(x.value.unwrap() as f32), node_id, index))
|
||||
.on_commit(commit_value)
|
||||
.widget_holder(),
|
||||
]),
|
||||
Some(&TaggedValue::U32(x)) => widgets.extend_from_slice(&[
|
||||
Separator::new(SeparatorType::Unrelated).widget_holder(),
|
||||
number_props
|
||||
@@ -883,6 +861,15 @@ pub fn number_widget(parameter_widgets_info: ParameterWidgetsInfo, number_props:
|
||||
.on_commit(commit_value)
|
||||
.widget_holder(),
|
||||
]),
|
||||
Some(&TaggedValue::FVec2(vec2)) => widgets.extend_from_slice(&[
|
||||
Separator::new(SeparatorType::Unrelated).widget_holder(),
|
||||
number_props
|
||||
// We use an arbitrary `y` instead of an arbitrary `x` here because the "Grid" node's "Spacing" value's height should be used from rectangular mode when transferred to "Y Spacing" in isometric mode
|
||||
.value(Some(vec2.y as f64))
|
||||
.on_update(update_value(move |x: &NumberInput| TaggedValue::F32(x.value.unwrap() as f32), node_id, index))
|
||||
.on_commit(commit_value)
|
||||
.widget_holder(),
|
||||
]),
|
||||
_ => {}
|
||||
}
|
||||
|
||||
@@ -941,35 +928,62 @@ pub fn color_widget(parameter_widgets_info: ParameterWidgetsInfo, color_button:
|
||||
|
||||
// Add the color input
|
||||
match &**tagged_value {
|
||||
TaggedValue::Color(color) => widgets.push(
|
||||
TaggedValue::ColorNotInTable(color) => widgets.push(
|
||||
color_button
|
||||
.value(FillChoice::Solid(*color))
|
||||
.on_update(update_value(|x: &ColorInput| TaggedValue::Color(x.value.as_solid().unwrap_or_default()), node_id, index))
|
||||
.allow_none(false)
|
||||
.on_update(update_value(|input: &ColorInput| TaggedValue::ColorNotInTable(input.value.as_solid().unwrap()), node_id, index))
|
||||
.on_commit(commit_value)
|
||||
.widget_holder(),
|
||||
),
|
||||
TaggedValue::OptionalColor(color) => widgets.push(
|
||||
TaggedValue::OptionalColorNotInTable(color) => widgets.push(
|
||||
color_button
|
||||
.value(match color {
|
||||
Some(color) => FillChoice::Solid(*color),
|
||||
.value(color.map_or(FillChoice::None, FillChoice::Solid))
|
||||
.allow_none(true)
|
||||
.on_update(update_value(|input: &ColorInput| TaggedValue::OptionalColorNotInTable(input.value.as_solid()), node_id, index))
|
||||
.on_commit(commit_value)
|
||||
.widget_holder(),
|
||||
),
|
||||
TaggedValue::Color(color_table) => widgets.push(
|
||||
color_button
|
||||
.value(match color_table.iter().next() {
|
||||
Some(color) => FillChoice::Solid(*color.element),
|
||||
None => FillChoice::None,
|
||||
})
|
||||
.on_update(update_value(|x: &ColorInput| TaggedValue::OptionalColor(x.value.as_solid()), node_id, index))
|
||||
.on_commit(commit_value)
|
||||
.widget_holder(),
|
||||
),
|
||||
TaggedValue::GradientStops(x) => widgets.push(
|
||||
color_button
|
||||
.value(FillChoice::Gradient(x.clone()))
|
||||
.on_update(update_value(
|
||||
|x: &ColorInput| TaggedValue::GradientStops(x.value.as_gradient().cloned().unwrap_or_default()),
|
||||
|input: &ColorInput| TaggedValue::Color(input.value.as_solid().iter().map(|&color| TableRow::new_from_element(color)).collect()),
|
||||
node_id,
|
||||
index,
|
||||
))
|
||||
.on_commit(commit_value)
|
||||
.widget_holder(),
|
||||
),
|
||||
_ => {}
|
||||
TaggedValue::GradientTable(gradient_table) => widgets.push(
|
||||
color_button
|
||||
.value(match gradient_table.iter().next() {
|
||||
Some(row) => FillChoice::Gradient(row.element.clone()),
|
||||
None => FillChoice::None,
|
||||
})
|
||||
.on_update(update_value(
|
||||
|input: &ColorInput| TaggedValue::GradientTable(input.value.as_gradient().iter().map(|&gradient| TableRow::new_from_element(gradient.clone())).collect()),
|
||||
node_id,
|
||||
index,
|
||||
))
|
||||
.on_commit(commit_value)
|
||||
.widget_holder(),
|
||||
),
|
||||
TaggedValue::GradientStops(gradient_stops) => widgets.push(
|
||||
color_button
|
||||
.value(FillChoice::Gradient(gradient_stops.clone()))
|
||||
.on_update(update_value(
|
||||
|input: &ColorInput| TaggedValue::GradientStops(input.value.as_gradient().cloned().unwrap_or_default()),
|
||||
node_id,
|
||||
index,
|
||||
))
|
||||
.on_commit(commit_value)
|
||||
.widget_holder(),
|
||||
),
|
||||
x => warn!("Colour {x:?}"),
|
||||
}
|
||||
|
||||
LayoutGroup::Row { widgets }
|
||||
@@ -1248,7 +1262,7 @@ pub(crate) fn grid_properties(node_id: NodeId, context: &mut NodePropertiesConte
|
||||
if let Some(&TaggedValue::GridType(grid_type)) = grid_type_input.as_non_exposed_value() {
|
||||
match grid_type {
|
||||
GridType::Rectangular => {
|
||||
let spacing = coordinate_widget(ParameterWidgetsInfo::new(node_id, SpacingInput::<f64>::INDEX, true, context), "W", "H", " px", Some(0.), false);
|
||||
let spacing = vec2_widget(ParameterWidgetsInfo::new(node_id, SpacingInput::<f64>::INDEX, true, context), "W", "H", " px", Some(0.), false);
|
||||
widgets.push(spacing);
|
||||
}
|
||||
GridType::Isometric => {
|
||||
@@ -1258,7 +1272,7 @@ pub(crate) fn grid_properties(node_id: NodeId, context: &mut NodePropertiesConte
|
||||
NumberInput::default().label("H").min(0.).unit(" px"),
|
||||
),
|
||||
};
|
||||
let angles = coordinate_widget(ParameterWidgetsInfo::new(node_id, AnglesInput::INDEX, true, context), "", "", "°", None, false);
|
||||
let angles = vec2_widget(ParameterWidgetsInfo::new(node_id, AnglesInput::INDEX, true, context), "", "", "°", None, false);
|
||||
widgets.extend([spacing, angles]);
|
||||
}
|
||||
}
|
||||
@@ -1592,7 +1606,7 @@ pub(crate) fn fill_properties(node_id: NodeId, context: &mut NodePropertiesConte
|
||||
}
|
||||
};
|
||||
|
||||
let (fill, backup_color, backup_gradient) = if let (Some(TaggedValue::Fill(fill)), &Some(&TaggedValue::OptionalColor(backup_color)), Some(TaggedValue::Gradient(backup_gradient))) = (
|
||||
let (fill, backup_color, backup_gradient) = if let (Some(TaggedValue::Fill(fill)), Some(TaggedValue::Color(backup_color)), Some(TaggedValue::Gradient(backup_gradient))) = (
|
||||
&document_node.inputs[FillInput::<Color>::INDEX].as_value(),
|
||||
&document_node.inputs[BackupColorInput::INDEX].as_value(),
|
||||
&document_node.inputs[BackupGradientInput::INDEX].as_value(),
|
||||
@@ -1602,7 +1616,7 @@ pub(crate) fn fill_properties(node_id: NodeId, context: &mut NodePropertiesConte
|
||||
return vec![LayoutGroup::Row { widgets: widgets_first_row }];
|
||||
};
|
||||
let fill2 = fill.clone();
|
||||
let backup_color_fill: Fill = backup_color.into();
|
||||
let backup_color_fill: Fill = backup_color.clone().into();
|
||||
let backup_gradient_fill: Fill = backup_gradient.clone().into();
|
||||
|
||||
widgets_first_row.push(Separator::new(SeparatorType::Unrelated).widget_holder());
|
||||
@@ -1615,13 +1629,13 @@ pub(crate) fn fill_properties(node_id: NodeId, context: &mut NodePropertiesConte
|
||||
Fill::None => NodeGraphMessage::SetInputValue {
|
||||
node_id,
|
||||
input_index: BackupColorInput::INDEX,
|
||||
value: TaggedValue::OptionalColor(None),
|
||||
value: TaggedValue::Color(Table::new()),
|
||||
}
|
||||
.into(),
|
||||
Fill::Solid(color) => NodeGraphMessage::SetInputValue {
|
||||
node_id,
|
||||
input_index: BackupColorInput::INDEX,
|
||||
value: TaggedValue::OptionalColor(Some(*color)),
|
||||
value: TaggedValue::Color(Table::new_from_element(*color)),
|
||||
}
|
||||
.into(),
|
||||
Fill::Gradient(gradient) => NodeGraphMessage::SetInputValue {
|
||||
@@ -1783,7 +1797,7 @@ pub fn stroke_properties(node_id: NodeId, context: &mut NodePropertiesContext) -
|
||||
let miter_limit_disabled = join_value != &StrokeJoin::Miter;
|
||||
|
||||
let color = color_widget(
|
||||
ParameterWidgetsInfo::new(node_id, ColorInput::<Option<Color>>::INDEX, true, context),
|
||||
ParameterWidgetsInfo::new(node_id, ColorInput::INDEX, true, context),
|
||||
crate::messages::layout::utility_types::widgets::button_widgets::ColorInput::default(),
|
||||
);
|
||||
let weight = number_widget(ParameterWidgetsInfo::new(node_id, WeightInput::INDEX, true, context), NumberInput::default().unit(" px").min(0.));
|
||||
@@ -1877,7 +1891,7 @@ pub fn math_properties(node_id: NodeId, context: &mut NodePropertiesContext) ->
|
||||
let mut expression = x.value.trim().to_string();
|
||||
|
||||
if ["+", "-", "*", "/", "^", "%"].iter().any(|&infix| infix == expression) {
|
||||
expression = format!("A {} B", expression);
|
||||
expression = format!("A {expression} B");
|
||||
} else if expression == "^" {
|
||||
expression = String::from("A^B");
|
||||
}
|
||||
@@ -1938,7 +1952,7 @@ pub mod choice {
|
||||
use super::ParameterWidgetsInfo;
|
||||
use crate::messages::tool::tool_messages::tool_prelude::*;
|
||||
use graph_craft::document::value::TaggedValue;
|
||||
use graphene_std::registry::{ChoiceTypeStatic, ChoiceWidgetHint};
|
||||
use graphene_std::choice_type::{ChoiceTypeStatic, ChoiceWidgetHint};
|
||||
use std::marker::PhantomData;
|
||||
|
||||
pub trait WidgetFactory {
|
||||
@@ -1992,16 +2006,13 @@ pub mod choice {
|
||||
{
|
||||
let items = E::list()
|
||||
.iter()
|
||||
.map(|group| {
|
||||
group
|
||||
.map(|section| {
|
||||
section
|
||||
.iter()
|
||||
.map(|(item, metadata)| {
|
||||
let updater = updater_factory();
|
||||
let committer = committer_factory();
|
||||
MenuListEntry::new(metadata.name.as_ref())
|
||||
.label(metadata.label.as_ref())
|
||||
.on_update(move |_| updater(item))
|
||||
.on_commit(committer)
|
||||
MenuListEntry::new(metadata.name).label(metadata.label).on_update(move |_| updater(item)).on_commit(committer)
|
||||
})
|
||||
.collect()
|
||||
})
|
||||
@@ -2016,15 +2027,15 @@ pub mod choice {
|
||||
{
|
||||
let items = E::list()
|
||||
.iter()
|
||||
.flat_map(|group| group.iter())
|
||||
.flat_map(|section| section.iter())
|
||||
.map(|(item, var_meta)| {
|
||||
let updater = updater_factory();
|
||||
let committer = committer_factory();
|
||||
let entry = RadioEntryData::new(var_meta.name.as_ref()).on_update(move |_| updater(item)).on_commit(committer);
|
||||
match (var_meta.icon.as_deref(), var_meta.docstring.as_deref()) {
|
||||
(None, None) => entry.label(var_meta.label.as_ref()),
|
||||
(None, Some(doc)) => entry.label(var_meta.label.as_ref()).tooltip(doc),
|
||||
(Some(icon), None) => entry.icon(icon).tooltip(var_meta.label.as_ref()),
|
||||
let entry = RadioEntryData::new(var_meta.name).on_update(move |_| updater(item)).on_commit(committer);
|
||||
match (var_meta.icon, var_meta.docstring) {
|
||||
(None, None) => entry.label(var_meta.label),
|
||||
(None, Some(doc)) => entry.label(var_meta.label).tooltip(doc),
|
||||
(Some(icon), None) => entry.icon(icon).tooltip(var_meta.label),
|
||||
(Some(icon), Some(doc)) => entry.icon(icon).tooltip(format!("{}\n\n{}", var_meta.label, doc)),
|
||||
}
|
||||
})
|
||||
@@ -2078,7 +2089,7 @@ pub mod choice {
|
||||
pub fn property_row(self) -> LayoutGroup {
|
||||
let ParameterWidgetsInfo { document_node, node_id, index, .. } = self.parameter_info;
|
||||
let Some(document_node) = document_node else {
|
||||
log::error!("Could not get document node when building property row for node {:?}", node_id);
|
||||
log::error!("Could not get document node when building property row for node {node_id:?}");
|
||||
return LayoutGroup::Row { widgets: Vec::new() };
|
||||
};
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use crate::messages::portfolio::document::utility_types::network_interface::{InputConnector, OutputConnector, TypeSource};
|
||||
use crate::messages::portfolio::document::utility_types::network_interface::TypeSource;
|
||||
use glam::IVec2;
|
||||
use graph_craft::document::NodeId;
|
||||
use graph_craft::document::value::TaggedValue;
|
||||
use graphene_std::Type;
|
||||
@@ -8,29 +9,35 @@ use std::borrow::Cow;
|
||||
pub enum FrontendGraphDataType {
|
||||
#[default]
|
||||
General,
|
||||
Raster,
|
||||
VectorData,
|
||||
Number,
|
||||
Group,
|
||||
Artboard,
|
||||
Graphic,
|
||||
Raster,
|
||||
Vector,
|
||||
Color,
|
||||
Gradient,
|
||||
Typography,
|
||||
}
|
||||
|
||||
impl FrontendGraphDataType {
|
||||
pub fn from_type(input: &Type) -> Self {
|
||||
match TaggedValue::from_type_or_none(input) {
|
||||
TaggedValue::Image(_) | TaggedValue::RasterData(_) => Self::Raster,
|
||||
TaggedValue::Subpaths(_) | TaggedValue::VectorData(_) => Self::VectorData,
|
||||
TaggedValue::U32(_)
|
||||
| TaggedValue::U64(_)
|
||||
| TaggedValue::F32(_)
|
||||
| TaggedValue::F64(_)
|
||||
| TaggedValue::DVec2(_)
|
||||
| TaggedValue::OptionalDVec2(_)
|
||||
| TaggedValue::F64Array4(_)
|
||||
| TaggedValue::VecF64(_)
|
||||
| TaggedValue::VecDVec2(_)
|
||||
| TaggedValue::DAffine2(_) => Self::Number,
|
||||
TaggedValue::GraphicGroup(_) | TaggedValue::GraphicElement(_) => Self::Group, // TODO: Is GraphicElement supposed to be included here?
|
||||
TaggedValue::ArtboardGroup(_) => Self::Artboard,
|
||||
TaggedValue::Artboard(_) => Self::Artboard,
|
||||
TaggedValue::Graphic(_) => Self::Graphic,
|
||||
TaggedValue::Raster(_) => Self::Raster,
|
||||
TaggedValue::Vector(_) => Self::Vector,
|
||||
TaggedValue::Color(_) => Self::Color,
|
||||
TaggedValue::Gradient(_) | TaggedValue::GradientStops(_) | TaggedValue::GradientTable(_) => Self::Gradient,
|
||||
TaggedValue::String(_) => Self::Typography,
|
||||
_ => Self::General,
|
||||
}
|
||||
}
|
||||
@@ -54,7 +61,8 @@ pub struct FrontendGraphInput {
|
||||
#[serde(rename = "validTypes")]
|
||||
pub valid_types: Vec<String>,
|
||||
#[serde(rename = "connectedTo")]
|
||||
pub connected_to: Option<OutputConnector>,
|
||||
/// Either "nothing", "import index {index}", or "{node name} output {output_index}".
|
||||
pub connected_to: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
|
||||
@@ -62,11 +70,13 @@ pub struct FrontendGraphOutput {
|
||||
#[serde(rename = "dataType")]
|
||||
pub data_type: FrontendGraphDataType,
|
||||
pub name: String,
|
||||
pub description: String,
|
||||
#[serde(rename = "resolvedType")]
|
||||
pub resolved_type: String,
|
||||
pub description: String,
|
||||
/// If connected to an export, it is "export index {index}".
|
||||
/// If connected to a node, it is "{node name} input {input_index}".
|
||||
#[serde(rename = "connectedTo")]
|
||||
pub connected_to: Vec<InputConnector>,
|
||||
pub connected_to: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
|
||||
@@ -87,13 +97,15 @@ pub struct FrontendNode {
|
||||
pub primary_output: Option<FrontendGraphOutput>,
|
||||
#[serde(rename = "exposedOutputs")]
|
||||
pub exposed_outputs: Vec<FrontendGraphOutput>,
|
||||
pub position: (i32, i32),
|
||||
#[serde(rename = "primaryOutputConnectedToLayer")]
|
||||
pub primary_output_connected_to_layer: bool,
|
||||
#[serde(rename = "primaryInputConnectedToLayer")]
|
||||
pub primary_input_connected_to_layer: bool,
|
||||
pub position: IVec2,
|
||||
pub visible: bool,
|
||||
pub locked: bool,
|
||||
pub previewed: bool,
|
||||
pub errors: Option<String>,
|
||||
#[serde(rename = "uiOnly")]
|
||||
pub ui_only: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
|
||||
@@ -178,8 +190,8 @@ pub struct FrontendClickTargets {
|
||||
pub node_click_targets: Vec<String>,
|
||||
#[serde(rename = "layerClickTargets")]
|
||||
pub layer_click_targets: Vec<String>,
|
||||
#[serde(rename = "portClickTargets")]
|
||||
pub port_click_targets: Vec<String>,
|
||||
#[serde(rename = "connectorClickTargets")]
|
||||
pub connector_click_targets: Vec<String>,
|
||||
#[serde(rename = "iconClickTargets")]
|
||||
pub icon_click_targets: Vec<String>,
|
||||
#[serde(rename = "allNodesBoundingBox")]
|
||||
|
||||
@@ -200,7 +200,7 @@ pub fn overlay_options(grid: &GridSnapping) -> Vec<LayoutGroup> {
|
||||
move |input: &I| {
|
||||
let mut grid = grid.clone();
|
||||
update(&mut grid, input);
|
||||
DocumentMessage::GridOptions(grid).into()
|
||||
DocumentMessage::GridOptions { options: grid }.into()
|
||||
}
|
||||
}
|
||||
let update_origin = |grid, update: fn(&mut GridSnapping) -> Option<&mut f64>| {
|
||||
|
||||
@@ -2,6 +2,7 @@ pub mod grid_overlays;
|
||||
mod overlays_message;
|
||||
mod overlays_message_handler;
|
||||
pub mod utility_functions;
|
||||
#[cfg_attr(not(target_family = "wasm"), path = "utility_types_vello.rs")]
|
||||
pub mod utility_types;
|
||||
|
||||
#[doc(inline)]
|
||||
|
||||
@@ -7,14 +7,14 @@ use crate::messages::prelude::*;
|
||||
pub enum OverlaysMessage {
|
||||
Draw,
|
||||
// Serde functionality isn't used but is required by the message system macros
|
||||
AddProvider(
|
||||
AddProvider {
|
||||
#[serde(skip, default = "empty_provider")]
|
||||
#[derivative(Debug = "ignore", PartialEq = "ignore")]
|
||||
OverlayProvider,
|
||||
),
|
||||
RemoveProvider(
|
||||
provider: OverlayProvider,
|
||||
},
|
||||
RemoveProvider {
|
||||
#[serde(skip, default = "empty_provider")]
|
||||
#[derivative(Debug = "ignore", PartialEq = "ignore")]
|
||||
OverlayProvider,
|
||||
),
|
||||
provider: OverlayProvider,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -11,21 +11,24 @@ pub struct OverlaysMessageContext<'a> {
|
||||
#[derive(Debug, Clone, Default, ExtractField)]
|
||||
pub struct OverlaysMessageHandler {
|
||||
pub overlay_providers: HashSet<OverlayProvider>,
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
#[cfg(target_family = "wasm")]
|
||||
canvas: Option<web_sys::HtmlCanvasElement>,
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
#[cfg(target_family = "wasm")]
|
||||
context: Option<web_sys::CanvasRenderingContext2d>,
|
||||
}
|
||||
|
||||
#[message_handler_data]
|
||||
impl MessageHandler<OverlaysMessage, OverlaysMessageContext<'_>> for OverlaysMessageHandler {
|
||||
fn process_message(&mut self, message: OverlaysMessage, responses: &mut VecDeque<Message>, context: OverlaysMessageContext) {
|
||||
let OverlaysMessageContext { visibility_settings, ipp, .. } = context;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
let device_pixel_ratio = context.device_pixel_ratio;
|
||||
let OverlaysMessageContext {
|
||||
visibility_settings,
|
||||
ipp,
|
||||
device_pixel_ratio,
|
||||
..
|
||||
} = context;
|
||||
|
||||
match message {
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
#[cfg(target_family = "wasm")]
|
||||
OverlaysMessage::Draw => {
|
||||
use super::utility_functions::overlay_canvas_element;
|
||||
use super::utility_types::OverlayContext;
|
||||
@@ -53,12 +56,14 @@ impl MessageHandler<OverlaysMessage, OverlaysMessageContext<'_>> for OverlaysMes
|
||||
let _ = canvas_context.reset_transform();
|
||||
|
||||
if visibility_settings.all() {
|
||||
responses.add(DocumentMessage::GridOverlays(OverlayContext {
|
||||
render_context: canvas_context.clone(),
|
||||
size: size.as_dvec2(),
|
||||
device_pixel_ratio,
|
||||
visibility_settings: visibility_settings.clone(),
|
||||
}));
|
||||
responses.add(DocumentMessage::GridOverlays {
|
||||
context: OverlayContext {
|
||||
render_context: canvas_context.clone(),
|
||||
size: size.as_dvec2(),
|
||||
device_pixel_ratio,
|
||||
visibility_settings: visibility_settings.clone(),
|
||||
},
|
||||
});
|
||||
for provider in &self.overlay_providers {
|
||||
responses.add(provider(OverlayContext {
|
||||
render_context: canvas_context.clone(),
|
||||
@@ -69,14 +74,31 @@ impl MessageHandler<OverlaysMessage, OverlaysMessageContext<'_>> for OverlaysMes
|
||||
}
|
||||
}
|
||||
}
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
#[cfg(all(not(target_family = "wasm"), not(test)))]
|
||||
OverlaysMessage::Draw => {
|
||||
warn!("Cannot render overlays on non-Wasm targets.\n{responses:?} {visibility_settings:?} {ipp:?}",);
|
||||
use super::utility_types::OverlayContext;
|
||||
|
||||
let size = ipp.viewport_bounds.size();
|
||||
|
||||
let overlay_context = OverlayContext::new(size, device_pixel_ratio, visibility_settings);
|
||||
|
||||
if visibility_settings.all() {
|
||||
responses.add(DocumentMessage::GridOverlays { context: overlay_context.clone() });
|
||||
|
||||
for provider in &self.overlay_providers {
|
||||
responses.add(provider(overlay_context.clone()));
|
||||
}
|
||||
}
|
||||
responses.add(FrontendMessage::RenderOverlays { context: overlay_context });
|
||||
}
|
||||
OverlaysMessage::AddProvider(message) => {
|
||||
#[cfg(all(not(target_family = "wasm"), test))]
|
||||
OverlaysMessage::Draw => {
|
||||
let _ = (responses, visibility_settings, ipp, device_pixel_ratio);
|
||||
}
|
||||
OverlaysMessage::AddProvider { provider: message } => {
|
||||
self.overlay_providers.insert(message);
|
||||
}
|
||||
OverlaysMessage::RemoveProvider(message) => {
|
||||
OverlaysMessage::RemoveProvider { provider: message } => {
|
||||
self.overlay_providers.remove(&message);
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
@@ -1,12 +1,14 @@
|
||||
use super::utility_types::{DrawHandles, OverlayContext};
|
||||
use crate::consts::HIDE_HANDLE_DISTANCE;
|
||||
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
||||
use crate::messages::portfolio::document::utility_types::network_interface::NodeNetworkInterface;
|
||||
use crate::messages::tool::common_functionality::shape_editor::{SelectedLayerState, ShapeState};
|
||||
use crate::messages::tool::tool_messages::tool_prelude::{DocumentMessageHandler, PreferencesMessageHandler};
|
||||
use bezier_rs::{Bezier, BezierHandles};
|
||||
use glam::{DAffine2, DVec2};
|
||||
use graphene_std::vector::ManipulatorPointId;
|
||||
use graphene_std::vector::{PointId, SegmentId};
|
||||
use graphene_std::subpath::{Bezier, BezierHandles};
|
||||
use graphene_std::vector::misc::ManipulatorPointId;
|
||||
use graphene_std::vector::{PointId, SegmentId, Vector};
|
||||
use std::collections::HashMap;
|
||||
use wasm_bindgen::JsCast;
|
||||
|
||||
pub fn overlay_canvas_element() -> Option<web_sys::HtmlCanvasElement> {
|
||||
@@ -24,33 +26,40 @@ pub fn overlay_canvas_context() -> web_sys::CanvasRenderingContext2d {
|
||||
create_context().expect("Failed to get canvas context")
|
||||
}
|
||||
|
||||
pub fn selected_segments(network_interface: &NodeNetworkInterface, shape_editor: &ShapeState) -> Vec<SegmentId> {
|
||||
let selected_points = shape_editor.selected_points();
|
||||
let selected_anchors = selected_points
|
||||
.filter_map(|point_id| if let ManipulatorPointId::Anchor(p) = point_id { Some(*p) } else { None })
|
||||
pub fn selected_segments(network_interface: &NodeNetworkInterface, shape_editor: &ShapeState) -> HashMap<LayerNodeIdentifier, Vec<SegmentId>> {
|
||||
let mut map = HashMap::new();
|
||||
|
||||
for (layer, state) in &shape_editor.selected_shape_state {
|
||||
let Some(vector) = network_interface.compute_modified_vector(*layer) else { continue };
|
||||
let selected_segments = selected_segments_for_layer(&vector, state);
|
||||
|
||||
map.insert(*layer, selected_segments);
|
||||
}
|
||||
|
||||
map
|
||||
}
|
||||
|
||||
pub fn selected_segments_for_layer(vector: &Vector, state: &SelectedLayerState) -> Vec<SegmentId> {
|
||||
let selected_anchors = state
|
||||
.selected_points()
|
||||
.filter_map(|point| if let ManipulatorPointId::Anchor(p) = point { Some(p) } else { None })
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// Collect the segments whose handles are selected
|
||||
let mut selected_segments = shape_editor
|
||||
let mut selected_segments = state
|
||||
.selected_points()
|
||||
.filter_map(|point_id| match point_id {
|
||||
ManipulatorPointId::PrimaryHandle(segment_id) | ManipulatorPointId::EndHandle(segment_id) => Some(*segment_id),
|
||||
ManipulatorPointId::PrimaryHandle(segment_id) | ManipulatorPointId::EndHandle(segment_id) => Some(segment_id),
|
||||
ManipulatorPointId::Anchor(_) => None,
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// TODO: Currently if there are two duplicate layers, both of their segments get overlays
|
||||
// Adding segments which are are connected to selected anchors
|
||||
for layer in network_interface.selected_nodes().selected_layers(network_interface.document_metadata()) {
|
||||
let Some(vector_data) = network_interface.compute_modified_vector(layer) else { continue };
|
||||
|
||||
for (segment_id, _bezier, start, end) in vector_data.segment_bezier_iter() {
|
||||
if selected_anchors.contains(&start) || selected_anchors.contains(&end) {
|
||||
selected_segments.push(segment_id);
|
||||
}
|
||||
for (segment_id, _bezier, start, end) in vector.segment_bezier_iter() {
|
||||
if selected_anchors.contains(&start) || selected_anchors.contains(&end) {
|
||||
selected_segments.push(segment_id);
|
||||
}
|
||||
}
|
||||
|
||||
selected_segments
|
||||
}
|
||||
|
||||
@@ -118,51 +127,54 @@ pub fn path_overlays(document: &DocumentMessageHandler, draw_handles: DrawHandle
|
||||
let display_anchors = overlay_context.visibility_settings.anchors();
|
||||
|
||||
for layer in document.network_interface.selected_nodes().selected_layers(document.metadata()) {
|
||||
let Some(vector_data) = document.network_interface.compute_modified_vector(layer) else { continue };
|
||||
let Some(vector) = document.network_interface.compute_modified_vector(layer) else { continue };
|
||||
let transform = document.metadata().transform_to_viewport_if_feeds(layer, &document.network_interface);
|
||||
if display_path {
|
||||
overlay_context.outline_vector(&vector_data, transform);
|
||||
overlay_context.outline_vector(&vector, transform);
|
||||
}
|
||||
|
||||
// Get the selected segments and then add a bold line overlay on them
|
||||
for (segment_id, bezier, _, _) in vector_data.segment_bezier_iter() {
|
||||
let Some(selected_shape_state) = shape_editor.selected_shape_state.get_mut(&layer) else {
|
||||
continue;
|
||||
};
|
||||
let Some(selected_shape_state) = shape_editor.selected_shape_state.get_mut(&layer) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
// Get the selected segments and then add a bold line overlay on them
|
||||
for (segment_id, bezier, _, _) in vector.segment_iter() {
|
||||
if selected_shape_state.is_segment_selected(segment_id) {
|
||||
overlay_context.outline_select_bezier(bezier, transform);
|
||||
}
|
||||
}
|
||||
|
||||
let selected = shape_editor.selected_shape_state.get(&layer);
|
||||
let is_selected = |point: ManipulatorPointId| selected.is_some_and(|selected| selected.is_point_selected(point));
|
||||
let is_selected = |point: ManipulatorPointId| selected_shape_state.is_point_selected(point);
|
||||
|
||||
if display_handles {
|
||||
let opposite_handles_data: Vec<(PointId, SegmentId)> = shape_editor.selected_points().filter_map(|point_id| vector_data.adjacent_segment(point_id)).collect();
|
||||
let opposite_handles_data = selected_shape_state.selected_points().filter_map(|point_id| vector.adjacent_segment(&point_id)).collect::<Vec<_>>();
|
||||
|
||||
match draw_handles {
|
||||
DrawHandles::All => {
|
||||
vector_data.segment_bezier_iter().for_each(|(segment_id, bezier, _start, _end)| {
|
||||
vector.segment_bezier_iter().for_each(|(segment_id, bezier, _start, _end)| {
|
||||
overlay_bezier_handles(bezier, segment_id, transform, is_selected, overlay_context);
|
||||
});
|
||||
}
|
||||
DrawHandles::SelectedAnchors(ref selected_segments) => {
|
||||
vector_data
|
||||
let Some(focused_segments) = selected_segments.get(&layer) else { continue };
|
||||
|
||||
vector
|
||||
.segment_bezier_iter()
|
||||
.filter(|(segment_id, ..)| selected_segments.contains(segment_id))
|
||||
.filter(|(segment_id, ..)| focused_segments.contains(segment_id))
|
||||
.for_each(|(segment_id, bezier, _start, _end)| {
|
||||
overlay_bezier_handles(bezier, segment_id, transform, is_selected, overlay_context);
|
||||
});
|
||||
|
||||
for (segment_id, bezier, start, end) in vector_data.segment_bezier_iter() {
|
||||
for (segment_id, bezier, start, end) in vector.segment_bezier_iter() {
|
||||
if let Some((corresponding_anchor, _)) = opposite_handles_data.iter().find(|(_, adj_segment_id)| adj_segment_id == &segment_id) {
|
||||
overlay_bezier_handle_specific_point(bezier, segment_id, (start, end), *corresponding_anchor, transform, is_selected, overlay_context);
|
||||
}
|
||||
}
|
||||
}
|
||||
DrawHandles::FrontierHandles(ref segment_endpoints) => {
|
||||
vector_data
|
||||
DrawHandles::FrontierHandles(ref segment_endpoints_by_layer) => {
|
||||
let Some(segment_endpoints) = segment_endpoints_by_layer.get(&layer) else { continue };
|
||||
|
||||
vector
|
||||
.segment_bezier_iter()
|
||||
.filter(|(segment_id, ..)| segment_endpoints.contains_key(segment_id))
|
||||
.for_each(|(segment_id, bezier, start, end)| {
|
||||
@@ -179,7 +191,7 @@ pub fn path_overlays(document: &DocumentMessageHandler, draw_handles: DrawHandle
|
||||
}
|
||||
|
||||
if display_anchors {
|
||||
for (&id, &position) in vector_data.point_domain.ids().iter().zip(vector_data.point_domain.positions()) {
|
||||
for (&id, &position) in vector.point_domain.ids().iter().zip(vector.point_domain.positions()) {
|
||||
overlay_context.manipulator_anchor(transform.transform_point2(position), is_selected(ManipulatorPointId::Anchor(id)), None);
|
||||
}
|
||||
}
|
||||
@@ -192,7 +204,7 @@ pub fn path_endpoint_overlays(document: &DocumentMessageHandler, shape_editor: &
|
||||
}
|
||||
|
||||
for layer in document.network_interface.selected_nodes().selected_layers(document.metadata()) {
|
||||
let Some(vector_data) = document.network_interface.compute_modified_vector(layer) else {
|
||||
let Some(vector) = document.network_interface.compute_modified_vector(layer) else {
|
||||
continue;
|
||||
};
|
||||
//let document_to_viewport = document.navigation_handler.calculate_offset_transform(overlay_context.size / 2., &document.document_ptz);
|
||||
@@ -200,8 +212,8 @@ pub fn path_endpoint_overlays(document: &DocumentMessageHandler, shape_editor: &
|
||||
let selected = shape_editor.selected_shape_state.get(&layer);
|
||||
let is_selected = |selected: Option<&SelectedLayerState>, point: ManipulatorPointId| selected.is_some_and(|selected| selected.is_point_selected(point));
|
||||
|
||||
for point in vector_data.extendable_points(preferences.vector_meshes) {
|
||||
let Some(position) = vector_data.point_domain.position_from_id(point) else { continue };
|
||||
for point in vector.extendable_points(preferences.vector_meshes) {
|
||||
let Some(position) = vector.point_domain.position_from_id(point) else { continue };
|
||||
let position = transform.transform_point2(position);
|
||||
overlay_context.manipulator_anchor(position, is_selected(selected, ManipulatorPointId::Anchor(point)), None);
|
||||
}
|
||||
|
||||
@@ -1,18 +1,21 @@
|
||||
use super::utility_functions::overlay_canvas_context;
|
||||
use crate::consts::{
|
||||
COLOR_OVERLAY_BLUE, COLOR_OVERLAY_BLUE_50, COLOR_OVERLAY_GREEN, COLOR_OVERLAY_RED, COLOR_OVERLAY_WHITE, COLOR_OVERLAY_YELLOW, COLOR_OVERLAY_YELLOW_DULL, COMPASS_ROSE_ARROW_SIZE,
|
||||
COMPASS_ROSE_HOVER_RING_DIAMETER, COMPASS_ROSE_MAIN_RING_DIAMETER, COMPASS_ROSE_RING_INNER_DIAMETER, DOWEL_PIN_RADIUS, MANIPULATOR_GROUP_MARKER_SIZE, PIVOT_CROSSHAIR_LENGTH,
|
||||
PIVOT_CROSSHAIR_THICKNESS, PIVOT_DIAMETER,
|
||||
ARC_SWEEP_GIZMO_RADIUS, COLOR_OVERLAY_BLUE, COLOR_OVERLAY_BLUE_50, COLOR_OVERLAY_GREEN, COLOR_OVERLAY_RED, COLOR_OVERLAY_WHITE, COLOR_OVERLAY_YELLOW, COLOR_OVERLAY_YELLOW_DULL,
|
||||
COMPASS_ROSE_ARROW_SIZE, COMPASS_ROSE_HOVER_RING_DIAMETER, COMPASS_ROSE_MAIN_RING_DIAMETER, COMPASS_ROSE_RING_INNER_DIAMETER, DOWEL_PIN_RADIUS, MANIPULATOR_GROUP_MARKER_SIZE,
|
||||
PIVOT_CROSSHAIR_LENGTH, PIVOT_CROSSHAIR_THICKNESS, PIVOT_DIAMETER, SEGMENT_SELECTED_THICKNESS,
|
||||
};
|
||||
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
||||
use crate::messages::prelude::Message;
|
||||
use bezier_rs::{Bezier, Subpath};
|
||||
use core::borrow::Borrow;
|
||||
use core::f64::consts::{FRAC_PI_2, PI, TAU};
|
||||
use glam::{DAffine2, DVec2};
|
||||
use graphene_std::Color;
|
||||
use graphene_std::math::quad::Quad;
|
||||
use graphene_std::subpath::Subpath;
|
||||
use graphene_std::vector::click_target::ClickTargetType;
|
||||
use graphene_std::vector::{PointId, SegmentId, VectorData};
|
||||
use graphene_std::vector::misc::{dvec2_to_point, point_to_dvec2};
|
||||
use graphene_std::vector::{PointId, SegmentId, Vector};
|
||||
use kurbo::{self, Affine, CubicBez, ParamCurve, PathSeg};
|
||||
use std::collections::HashMap;
|
||||
use wasm_bindgen::{JsCast, JsValue};
|
||||
use web_sys::{OffscreenCanvas, OffscreenCanvasRenderingContext2d};
|
||||
@@ -23,7 +26,7 @@ pub fn empty_provider() -> OverlayProvider {
|
||||
|_| Message::NoOp
|
||||
}
|
||||
|
||||
// Types of overlays used by DocumentMessage to enable/disable select group of overlays in the frontend
|
||||
/// Types of overlays used by DocumentMessage to enable/disable the selected set of viewport overlays.
|
||||
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)]
|
||||
pub enum OverlaysType {
|
||||
ArtboardName,
|
||||
@@ -294,6 +297,147 @@ impl OverlayContext {
|
||||
self.end_dpi_aware_transform();
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn dashed_ellipse(
|
||||
&mut self,
|
||||
center: DVec2,
|
||||
radius_x: f64,
|
||||
radius_y: f64,
|
||||
rotation: Option<f64>,
|
||||
start_angle: Option<f64>,
|
||||
end_angle: Option<f64>,
|
||||
counterclockwise: Option<bool>,
|
||||
color_fill: Option<&str>,
|
||||
color_stroke: Option<&str>,
|
||||
dash_width: Option<f64>,
|
||||
dash_gap_width: Option<f64>,
|
||||
dash_offset: Option<f64>,
|
||||
) {
|
||||
let color_stroke = color_stroke.unwrap_or(COLOR_OVERLAY_BLUE);
|
||||
let center = center.round();
|
||||
|
||||
self.start_dpi_aware_transform();
|
||||
|
||||
if let Some(dash_width) = dash_width {
|
||||
let dash_gap_width = dash_gap_width.unwrap_or(1.);
|
||||
let array = js_sys::Array::new();
|
||||
array.push(&JsValue::from(dash_width));
|
||||
array.push(&JsValue::from(dash_gap_width));
|
||||
|
||||
if let Some(dash_offset) = dash_offset {
|
||||
if dash_offset != 0. {
|
||||
self.render_context.set_line_dash_offset(dash_offset);
|
||||
}
|
||||
}
|
||||
|
||||
self.render_context
|
||||
.set_line_dash(&JsValue::from(array))
|
||||
.map_err(|error| log::warn!("Error drawing dashed line: {:?}", error))
|
||||
.ok();
|
||||
}
|
||||
|
||||
self.render_context.begin_path();
|
||||
self.render_context
|
||||
.ellipse_with_anticlockwise(
|
||||
center.x,
|
||||
center.y,
|
||||
radius_x,
|
||||
radius_y,
|
||||
rotation.unwrap_or_default(),
|
||||
start_angle.unwrap_or_default(),
|
||||
end_angle.unwrap_or(TAU),
|
||||
counterclockwise.unwrap_or_default(),
|
||||
)
|
||||
.expect("Failed to draw ellipse");
|
||||
self.render_context.set_stroke_style_str(color_stroke);
|
||||
|
||||
if let Some(fill_color) = color_fill {
|
||||
self.render_context.set_fill_style_str(fill_color);
|
||||
self.render_context.fill();
|
||||
}
|
||||
self.render_context.stroke();
|
||||
|
||||
// Reset the dash pattern back to solid
|
||||
if dash_width.is_some() {
|
||||
self.render_context
|
||||
.set_line_dash(&JsValue::from(js_sys::Array::new()))
|
||||
.map_err(|error| log::warn!("Error drawing dashed line: {:?}", error))
|
||||
.ok();
|
||||
}
|
||||
if dash_offset.is_some() && dash_offset != Some(0.) {
|
||||
self.render_context.set_line_dash_offset(0.);
|
||||
}
|
||||
|
||||
self.end_dpi_aware_transform();
|
||||
}
|
||||
|
||||
pub fn dashed_circle(
|
||||
&mut self,
|
||||
position: DVec2,
|
||||
radius: f64,
|
||||
color_fill: Option<&str>,
|
||||
color_stroke: Option<&str>,
|
||||
dash_width: Option<f64>,
|
||||
dash_gap_width: Option<f64>,
|
||||
dash_offset: Option<f64>,
|
||||
transform: Option<DAffine2>,
|
||||
) {
|
||||
let color_stroke = color_stroke.unwrap_or(COLOR_OVERLAY_BLUE);
|
||||
let position = position.round();
|
||||
|
||||
self.start_dpi_aware_transform();
|
||||
|
||||
if let Some(transform) = transform {
|
||||
let [a, b, c, d, e, f] = transform.to_cols_array();
|
||||
self.render_context.transform(a, b, c, d, e, f).expect("Failed to transform circle");
|
||||
}
|
||||
|
||||
if let Some(dash_width) = dash_width {
|
||||
let dash_gap_width = dash_gap_width.unwrap_or(1.);
|
||||
let array = js_sys::Array::new();
|
||||
array.push(&JsValue::from(dash_width));
|
||||
array.push(&JsValue::from(dash_gap_width));
|
||||
|
||||
if let Some(dash_offset) = dash_offset {
|
||||
if dash_offset != 0. {
|
||||
self.render_context.set_line_dash_offset(dash_offset);
|
||||
}
|
||||
}
|
||||
|
||||
self.render_context
|
||||
.set_line_dash(&JsValue::from(array))
|
||||
.map_err(|error| log::warn!("Error drawing dashed line: {:?}", error))
|
||||
.ok();
|
||||
}
|
||||
|
||||
self.render_context.begin_path();
|
||||
self.render_context.arc(position.x, position.y, radius, 0., TAU).expect("Failed to draw the circle");
|
||||
self.render_context.set_stroke_style_str(color_stroke);
|
||||
|
||||
if let Some(fill_color) = color_fill {
|
||||
self.render_context.set_fill_style_str(fill_color);
|
||||
self.render_context.fill();
|
||||
}
|
||||
self.render_context.stroke();
|
||||
|
||||
// Reset the dash pattern back to solid
|
||||
if dash_width.is_some() {
|
||||
self.render_context
|
||||
.set_line_dash(&JsValue::from(js_sys::Array::new()))
|
||||
.map_err(|error| log::warn!("Error drawing dashed line: {:?}", error))
|
||||
.ok();
|
||||
}
|
||||
if dash_offset.is_some() && dash_offset != Some(0.) {
|
||||
self.render_context.set_line_dash_offset(0.);
|
||||
}
|
||||
|
||||
self.end_dpi_aware_transform();
|
||||
}
|
||||
|
||||
pub fn circle(&mut self, position: DVec2, radius: f64, color_fill: Option<&str>, color_stroke: Option<&str>) {
|
||||
self.dashed_circle(position, radius, color_fill, color_stroke, None, None, None, None);
|
||||
}
|
||||
|
||||
pub fn manipulator_handle(&mut self, position: DVec2, selected: bool, color: Option<&str>) {
|
||||
self.start_dpi_aware_transform();
|
||||
|
||||
@@ -319,6 +463,42 @@ impl OverlayContext {
|
||||
self.square(position, None, Some(color_fill), Some(color_stroke));
|
||||
}
|
||||
|
||||
pub fn hover_manipulator_handle(&mut self, position: DVec2, selected: bool) {
|
||||
self.start_dpi_aware_transform();
|
||||
|
||||
let position = position.round() - DVec2::splat(0.5);
|
||||
|
||||
self.render_context.begin_path();
|
||||
self.render_context
|
||||
.arc(position.x, position.y, (MANIPULATOR_GROUP_MARKER_SIZE + 2.) / 2., 0., TAU)
|
||||
.expect("Failed to draw the circle");
|
||||
|
||||
self.render_context.set_fill_style_str(COLOR_OVERLAY_BLUE_50);
|
||||
self.render_context.set_stroke_style_str(COLOR_OVERLAY_BLUE_50);
|
||||
self.render_context.fill();
|
||||
self.render_context.stroke();
|
||||
|
||||
self.render_context.begin_path();
|
||||
self.render_context
|
||||
.arc(position.x, position.y, MANIPULATOR_GROUP_MARKER_SIZE / 2., 0., TAU)
|
||||
.expect("Failed to draw the circle");
|
||||
|
||||
let color_fill = if selected { COLOR_OVERLAY_BLUE } else { COLOR_OVERLAY_WHITE };
|
||||
|
||||
self.render_context.set_fill_style_str(color_fill);
|
||||
self.render_context.set_stroke_style_str(COLOR_OVERLAY_BLUE);
|
||||
self.render_context.fill();
|
||||
self.render_context.stroke();
|
||||
|
||||
self.end_dpi_aware_transform();
|
||||
}
|
||||
|
||||
pub fn hover_manipulator_anchor(&mut self, position: DVec2, selected: bool) {
|
||||
self.square(position, Some(MANIPULATOR_GROUP_MARKER_SIZE + 2.), Some(COLOR_OVERLAY_BLUE_50), Some(COLOR_OVERLAY_BLUE_50));
|
||||
let color_fill = if selected { COLOR_OVERLAY_BLUE } else { COLOR_OVERLAY_WHITE };
|
||||
self.square(position, None, Some(color_fill), Some(COLOR_OVERLAY_BLUE));
|
||||
}
|
||||
|
||||
/// Transforms the canvas context to adjust for DPI scaling
|
||||
///
|
||||
/// Overwrites all existing tranforms. This operation can be reversed with [`Self::reset_transform`].
|
||||
@@ -374,23 +554,6 @@ impl OverlayContext {
|
||||
self.end_dpi_aware_transform();
|
||||
}
|
||||
|
||||
pub fn circle(&mut self, position: DVec2, radius: f64, color_fill: Option<&str>, color_stroke: Option<&str>) {
|
||||
let color_fill = color_fill.unwrap_or(COLOR_OVERLAY_WHITE);
|
||||
let color_stroke = color_stroke.unwrap_or(COLOR_OVERLAY_BLUE);
|
||||
let position = position.round();
|
||||
|
||||
self.start_dpi_aware_transform();
|
||||
|
||||
self.render_context.begin_path();
|
||||
self.render_context.arc(position.x, position.y, radius, 0., TAU).expect("Failed to draw the circle");
|
||||
self.render_context.set_fill_style_str(color_fill);
|
||||
self.render_context.set_stroke_style_str(color_stroke);
|
||||
self.render_context.fill();
|
||||
self.render_context.stroke();
|
||||
|
||||
self.end_dpi_aware_transform();
|
||||
}
|
||||
|
||||
pub fn draw_arc(&mut self, center: DVec2, radius: f64, start_from: f64, end_at: f64) {
|
||||
let segments = ((end_at - start_from).abs() / (std::f64::consts::PI / 4.)).ceil() as usize;
|
||||
let step = (end_at - start_from) / segments as f64;
|
||||
@@ -411,11 +574,7 @@ impl OverlayContext {
|
||||
let handle_start = start + start_vec.perp() * radius * factor;
|
||||
let handle_end = end - end_vec.perp() * radius * factor;
|
||||
|
||||
let bezier = Bezier {
|
||||
start,
|
||||
end,
|
||||
handles: bezier_rs::BezierHandles::Cubic { handle_start, handle_end },
|
||||
};
|
||||
let bezier = PathSeg::Cubic(CubicBez::new(dvec2_to_point(start), dvec2_to_point(handle_start), dvec2_to_point(handle_end), dvec2_to_point(end)));
|
||||
|
||||
self.bezier_command(bezier, DAffine2::IDENTITY, i == 0);
|
||||
}
|
||||
@@ -423,6 +582,12 @@ impl OverlayContext {
|
||||
self.render_context.stroke();
|
||||
}
|
||||
|
||||
pub fn draw_arc_gizmo_angle(&mut self, pivot: DVec2, bold_radius: f64, arc_radius: f64, offset_angle: f64, angle: f64) {
|
||||
let end_point1 = pivot + bold_radius * DVec2::from_angle(angle + offset_angle);
|
||||
self.line(pivot, end_point1, None, None);
|
||||
self.draw_arc(pivot, arc_radius, offset_angle, (angle) % TAU + offset_angle);
|
||||
}
|
||||
|
||||
pub fn draw_angle(&mut self, pivot: DVec2, radius: f64, arc_radius: f64, offset_angle: f64, angle: f64) {
|
||||
let end_point1 = pivot + radius * DVec2::from_angle(angle + offset_angle);
|
||||
let end_point2 = pivot + radius * DVec2::from_angle(offset_angle);
|
||||
@@ -584,13 +749,19 @@ impl OverlayContext {
|
||||
self.end_dpi_aware_transform();
|
||||
}
|
||||
|
||||
pub fn arc_sweep_angle(&mut self, offset_angle: f64, angle: f64, end_point_position: DVec2, bold_radius: f64, pivot: DVec2, text: &str, transform: DAffine2) {
|
||||
self.manipulator_handle(end_point_position, true, None);
|
||||
self.draw_arc_gizmo_angle(pivot, bold_radius, ARC_SWEEP_GIZMO_RADIUS, offset_angle, angle.to_radians());
|
||||
self.text(&text, COLOR_OVERLAY_BLUE, None, transform, 16., [Pivot::Middle, Pivot::Middle]);
|
||||
}
|
||||
|
||||
/// Used by the Pen and Path tools to outline the path of the shape.
|
||||
pub fn outline_vector(&mut self, vector_data: &VectorData, transform: DAffine2) {
|
||||
pub fn outline_vector(&mut self, vector: &Vector, transform: DAffine2) {
|
||||
self.start_dpi_aware_transform();
|
||||
|
||||
self.render_context.begin_path();
|
||||
let mut last_point = None;
|
||||
for (_, bezier, start_id, end_id) in vector_data.segment_bezier_iter() {
|
||||
for (_, bezier, start_id, end_id) in vector.segment_iter() {
|
||||
let move_to = last_point != Some(start_id);
|
||||
last_point = Some(end_id);
|
||||
|
||||
@@ -604,7 +775,7 @@ impl OverlayContext {
|
||||
}
|
||||
|
||||
/// Used by the Pen tool in order to show how the bezier curve would look like.
|
||||
pub fn outline_bezier(&mut self, bezier: Bezier, transform: DAffine2) {
|
||||
pub fn outline_bezier(&mut self, bezier: PathSeg, transform: DAffine2) {
|
||||
self.start_dpi_aware_transform();
|
||||
|
||||
self.render_context.begin_path();
|
||||
@@ -616,13 +787,13 @@ impl OverlayContext {
|
||||
}
|
||||
|
||||
/// Used by the path tool segment mode in order to show the selected segments.
|
||||
pub fn outline_select_bezier(&mut self, bezier: Bezier, transform: DAffine2) {
|
||||
pub fn outline_select_bezier(&mut self, bezier: PathSeg, transform: DAffine2) {
|
||||
self.start_dpi_aware_transform();
|
||||
|
||||
self.render_context.begin_path();
|
||||
self.bezier_command(bezier, transform, true);
|
||||
self.render_context.set_stroke_style_str(COLOR_OVERLAY_BLUE);
|
||||
self.render_context.set_line_width(4.);
|
||||
self.render_context.set_line_width(SEGMENT_SELECTED_THICKNESS);
|
||||
self.render_context.stroke();
|
||||
|
||||
self.render_context.set_line_width(1.);
|
||||
@@ -630,13 +801,13 @@ impl OverlayContext {
|
||||
self.end_dpi_aware_transform();
|
||||
}
|
||||
|
||||
pub fn outline_overlay_bezier(&mut self, bezier: Bezier, transform: DAffine2) {
|
||||
pub fn outline_overlay_bezier(&mut self, bezier: PathSeg, transform: DAffine2) {
|
||||
self.start_dpi_aware_transform();
|
||||
|
||||
self.render_context.begin_path();
|
||||
self.bezier_command(bezier, transform, true);
|
||||
self.render_context.set_stroke_style_str(COLOR_OVERLAY_BLUE_50);
|
||||
self.render_context.set_line_width(4.);
|
||||
self.render_context.set_line_width(SEGMENT_SELECTED_THICKNESS);
|
||||
self.render_context.stroke();
|
||||
|
||||
self.render_context.set_line_width(1.);
|
||||
@@ -644,18 +815,18 @@ impl OverlayContext {
|
||||
self.end_dpi_aware_transform();
|
||||
}
|
||||
|
||||
fn bezier_command(&self, bezier: Bezier, transform: DAffine2, move_to: bool) {
|
||||
fn bezier_command(&self, bezier: PathSeg, transform: DAffine2, move_to: bool) {
|
||||
self.start_dpi_aware_transform();
|
||||
|
||||
let Bezier { start, end, handles } = bezier.apply_transformation(|point| transform.transform_point2(point));
|
||||
let bezier = Affine::new(transform.to_cols_array()) * bezier;
|
||||
if move_to {
|
||||
self.render_context.move_to(start.x, start.y);
|
||||
self.render_context.move_to(bezier.start().x, bezier.start().y);
|
||||
}
|
||||
|
||||
match handles {
|
||||
bezier_rs::BezierHandles::Linear => self.render_context.line_to(end.x, end.y),
|
||||
bezier_rs::BezierHandles::Quadratic { handle } => self.render_context.quadratic_curve_to(handle.x, handle.y, end.x, end.y),
|
||||
bezier_rs::BezierHandles::Cubic { handle_start, handle_end } => self.render_context.bezier_curve_to(handle_start.x, handle_start.y, handle_end.x, handle_end.y, end.x, end.y),
|
||||
match bezier.as_path_el() {
|
||||
kurbo::PathEl::LineTo(point) => self.render_context.line_to(point.x, point.y),
|
||||
kurbo::PathEl::QuadTo(point, point1) => self.render_context.quadratic_curve_to(point.x, point.y, point1.x, point1.y),
|
||||
kurbo::PathEl::CurveTo(point, point1, point2) => self.render_context.bezier_curve_to(point.x, point.y, point1.x, point1.y, point2.x, point2.y),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
||||
self.end_dpi_aware_transform();
|
||||
@@ -669,36 +840,35 @@ impl OverlayContext {
|
||||
let subpath = subpath.borrow();
|
||||
let mut curves = subpath.iter().peekable();
|
||||
|
||||
let Some(first) = curves.peek() else {
|
||||
let Some(&first) = curves.peek() else {
|
||||
continue;
|
||||
};
|
||||
|
||||
self.render_context.move_to(transform.transform_point2(first.start()).x, transform.transform_point2(first.start()).y);
|
||||
for curve in curves {
|
||||
match curve.handles {
|
||||
bezier_rs::BezierHandles::Linear => {
|
||||
let a = transform.transform_point2(curve.end());
|
||||
let a = a.round() - DVec2::splat(0.5);
|
||||
let start_point = transform.transform_point2(point_to_dvec2(first.start()));
|
||||
self.render_context.move_to(start_point.x, start_point.y);
|
||||
|
||||
self.render_context.line_to(a.x, a.y)
|
||||
for curve in curves {
|
||||
match curve {
|
||||
PathSeg::Line(line) => {
|
||||
let a = transform.transform_point2(point_to_dvec2(line.p1));
|
||||
let a = a.round() - DVec2::splat(0.5);
|
||||
self.render_context.line_to(a.x, a.y);
|
||||
}
|
||||
bezier_rs::BezierHandles::Quadratic { handle } => {
|
||||
let a = transform.transform_point2(handle);
|
||||
let b = transform.transform_point2(curve.end());
|
||||
PathSeg::Quad(quad_bez) => {
|
||||
let a = transform.transform_point2(point_to_dvec2(quad_bez.p1));
|
||||
let b = transform.transform_point2(point_to_dvec2(quad_bez.p2));
|
||||
let a = a.round() - DVec2::splat(0.5);
|
||||
let b = b.round() - DVec2::splat(0.5);
|
||||
|
||||
self.render_context.quadratic_curve_to(a.x, a.y, b.x, b.y)
|
||||
self.render_context.quadratic_curve_to(a.x, a.y, b.x, b.y);
|
||||
}
|
||||
bezier_rs::BezierHandles::Cubic { handle_start, handle_end } => {
|
||||
let a = transform.transform_point2(handle_start);
|
||||
let b = transform.transform_point2(handle_end);
|
||||
let c = transform.transform_point2(curve.end());
|
||||
PathSeg::Cubic(cubic_bez) => {
|
||||
let a = transform.transform_point2(point_to_dvec2(cubic_bez.p1));
|
||||
let b = transform.transform_point2(point_to_dvec2(cubic_bez.p2));
|
||||
let c = transform.transform_point2(point_to_dvec2(cubic_bez.p3));
|
||||
let a = a.round() - DVec2::splat(0.5);
|
||||
let b = b.round() - DVec2::splat(0.5);
|
||||
let c = c.round() - DVec2::splat(0.5);
|
||||
|
||||
self.render_context.bezier_curve_to(a.x, a.y, b.x, b.y, c.x, c.y)
|
||||
self.render_context.bezier_curve_to(a.x, a.y, b.x, b.y, c.x, c.y);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -713,7 +883,7 @@ impl OverlayContext {
|
||||
|
||||
/// Used by the Select tool to outline a path or a free point when selected or hovered.
|
||||
pub fn outline(&mut self, target_types: impl Iterator<Item = impl Borrow<ClickTargetType>>, transform: DAffine2, color: Option<&str>) {
|
||||
let mut subpaths: Vec<bezier_rs::Subpath<PointId>> = vec![];
|
||||
let mut subpaths: Vec<Subpath<PointId>> = vec![];
|
||||
|
||||
target_types.for_each(|target_type| match target_type.borrow() {
|
||||
ClickTargetType::FreePoint(point) => {
|
||||
@@ -855,7 +1025,7 @@ pub enum Pivot {
|
||||
|
||||
pub enum DrawHandles {
|
||||
All,
|
||||
SelectedAnchors(Vec<SegmentId>),
|
||||
FrontierHandles(HashMap<SegmentId, Vec<PointId>>),
|
||||
SelectedAnchors(HashMap<LayerNodeIdentifier, Vec<SegmentId>>),
|
||||
FrontierHandles(HashMap<LayerNodeIdentifier, HashMap<SegmentId, Vec<PointId>>>),
|
||||
None,
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
+9
-2
@@ -14,6 +14,7 @@ pub struct PropertiesPanelMessageContext<'a> {
|
||||
pub document_name: &'a str,
|
||||
pub executor: &'a mut NodeGraphExecutor,
|
||||
pub persistent_data: &'a PersistentData,
|
||||
pub properties_panel_open: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, ExtractField)]
|
||||
@@ -28,16 +29,22 @@ impl MessageHandler<PropertiesPanelMessage, PropertiesPanelMessageContext<'_>> f
|
||||
document_name,
|
||||
executor,
|
||||
persistent_data,
|
||||
properties_panel_open,
|
||||
} = context;
|
||||
|
||||
match message {
|
||||
PropertiesPanelMessage::Clear => {
|
||||
responses.add(LayoutMessage::SendLayout {
|
||||
layout: Layout::WidgetLayout(WidgetLayout::new(vec![])),
|
||||
layout_target: LayoutTarget::PropertiesSections,
|
||||
layout_target: LayoutTarget::PropertiesPanel,
|
||||
});
|
||||
}
|
||||
PropertiesPanelMessage::Refresh => {
|
||||
if !properties_panel_open {
|
||||
responses.add(PropertiesPanelMessage::Clear);
|
||||
return;
|
||||
}
|
||||
|
||||
let mut node_properties_context = NodePropertiesContext {
|
||||
persistent_data,
|
||||
responses,
|
||||
@@ -50,7 +57,7 @@ impl MessageHandler<PropertiesPanelMessage, PropertiesPanelMessageContext<'_>> f
|
||||
|
||||
node_properties_context.responses.add(LayoutMessage::SendLayout {
|
||||
layout: Layout::WidgetLayout(WidgetLayout::new(properties_sections)),
|
||||
layout_target: LayoutTarget::PropertiesSections,
|
||||
layout_target: LayoutTarget::PropertiesPanel,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,9 +6,10 @@ use crate::messages::tool::common_functionality::graph_modification_utils;
|
||||
use glam::{DAffine2, DVec2};
|
||||
use graph_craft::document::NodeId;
|
||||
use graphene_std::math::quad::Quad;
|
||||
use graphene_std::subpath;
|
||||
use graphene_std::transform::Footprint;
|
||||
use graphene_std::vector::click_target::{ClickTarget, ClickTargetType};
|
||||
use graphene_std::vector::{PointId, VectorData};
|
||||
use graphene_std::vector::{PointId, Vector};
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::num::NonZeroU64;
|
||||
|
||||
@@ -22,11 +23,11 @@ use std::num::NonZeroU64;
|
||||
pub struct DocumentMetadata {
|
||||
pub upstream_footprints: HashMap<NodeId, Footprint>,
|
||||
pub local_transforms: HashMap<NodeId, DAffine2>,
|
||||
pub first_instance_source_ids: HashMap<NodeId, Option<NodeId>>,
|
||||
pub first_element_source_ids: HashMap<NodeId, Option<NodeId>>,
|
||||
pub structure: HashMap<LayerNodeIdentifier, NodeRelations>,
|
||||
pub click_targets: HashMap<LayerNodeIdentifier, Vec<ClickTarget>>,
|
||||
pub clip_targets: HashSet<NodeId>,
|
||||
pub vector_modify: HashMap<NodeId, VectorData>,
|
||||
pub vector_modify: HashMap<NodeId, Vector>,
|
||||
/// Transform from document space to viewport space.
|
||||
pub document_to_viewport: DAffine2,
|
||||
}
|
||||
@@ -90,8 +91,8 @@ impl DocumentMetadata {
|
||||
|
||||
let mut use_local = true;
|
||||
let graph_layer = graph_modification_utils::NodeGraphLayer::new(layer, network_interface);
|
||||
if let Some(path_node) = graph_layer.upstream_node_id_from_name("Path") {
|
||||
if let Some(&source) = self.first_instance_source_ids.get(&layer.to_node()) {
|
||||
if let Some(path_node) = graph_layer.upstream_visible_node_id_from_name_in_layer("Path") {
|
||||
if let Some(&source) = self.first_element_source_ids.get(&layer.to_node()) {
|
||||
if !network_interface
|
||||
.upstream_flow_back_from_nodes(vec![path_node], &[], FlowType::HorizontalFlow)
|
||||
.any(|upstream| Some(upstream) == source)
|
||||
@@ -160,6 +161,17 @@ impl DocumentMetadata {
|
||||
.reduce(Quad::combine_bounds)
|
||||
}
|
||||
|
||||
/// Get the loose bounding box of the click target of the specified layer in the specified transform space
|
||||
pub fn loose_bounding_box_with_transform(&self, layer: LayerNodeIdentifier, transform: DAffine2) -> Option<[DVec2; 2]> {
|
||||
self.click_targets(layer)?
|
||||
.iter()
|
||||
.filter_map(|click_target| match click_target.target_type() {
|
||||
ClickTargetType::Subpath(subpath) => subpath.loose_bounding_box_with_transform(transform),
|
||||
ClickTargetType::FreePoint(_) => click_target.bounding_box_with_transform(transform),
|
||||
})
|
||||
.reduce(Quad::combine_bounds)
|
||||
}
|
||||
|
||||
/// Calculate the corners of the bounding box but with a nonzero size.
|
||||
///
|
||||
/// If the layer bounds are `0` in either axis then they are changed to be `1`.
|
||||
@@ -196,7 +208,7 @@ impl DocumentMetadata {
|
||||
self.all_layers().filter_map(|layer| self.bounding_box_viewport(layer)).reduce(Quad::combine_bounds)
|
||||
}
|
||||
|
||||
pub fn layer_outline(&self, layer: LayerNodeIdentifier) -> impl Iterator<Item = &bezier_rs::Subpath<PointId>> {
|
||||
pub fn layer_outline(&self, layer: LayerNodeIdentifier) -> impl Iterator<Item = &subpath::Subpath<PointId>> {
|
||||
static EMPTY: Vec<ClickTarget> = Vec::new();
|
||||
let click_targets = self.click_targets.get(&layer).unwrap_or(&EMPTY);
|
||||
click_targets.iter().filter_map(|target| match target.target_type() {
|
||||
@@ -303,10 +315,10 @@ impl LayerNodeIdentifier {
|
||||
child.ancestors(metadata).any(|ancestor| ancestor == self)
|
||||
}
|
||||
|
||||
/// Is the layer last child of parent group? Used for clipping
|
||||
/// Is the layer the last child of its stack? Used for clipping
|
||||
pub fn can_be_clipped(self, metadata: &DocumentMetadata) -> bool {
|
||||
self.parent(metadata)
|
||||
.map_or(false, |layer| layer.last_child(metadata).expect("Parent accessed via child should have children") != self)
|
||||
.is_some_and(|layer| layer.last_child(metadata).expect("Parent accessed via child should have children") != self)
|
||||
}
|
||||
|
||||
/// Iterator over all direct children (excluding self and recursive children)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
+136
@@ -0,0 +1,136 @@
|
||||
use crate::messages::portfolio::document::utility_types::network_interface::{DocumentNodePersistentMetadata, InputMetadata, InputPersistentMetadata, NodeNetworkMetadata, NodeTypePersistentMetadata};
|
||||
use serde_json::Value;
|
||||
use std::collections::HashMap;
|
||||
|
||||
/// Persistent metadata for each node in the network, which must be included when creating, serializing, and deserializing saving a node.
|
||||
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||
pub struct DocumentNodePersistentMetadataInputNames {
|
||||
pub reference: Option<String>,
|
||||
#[serde(default)]
|
||||
pub display_name: String,
|
||||
pub input_names: Vec<String>,
|
||||
pub output_names: Vec<String>,
|
||||
pub has_primary_output: bool,
|
||||
#[serde(default)]
|
||||
pub locked: bool,
|
||||
#[serde(default)]
|
||||
pub pinned: bool,
|
||||
pub node_type_metadata: NodeTypePersistentMetadata,
|
||||
pub network_metadata: Option<NodeNetworkMetadata>,
|
||||
}
|
||||
|
||||
impl From<DocumentNodePersistentMetadataInputNames> for DocumentNodePersistentMetadata {
|
||||
fn from(old: DocumentNodePersistentMetadataInputNames) -> Self {
|
||||
DocumentNodePersistentMetadata {
|
||||
input_metadata: Vec::new(),
|
||||
..old.into()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||
pub struct DocumentNodePersistentMetadataPropertiesRow {
|
||||
pub reference: Option<String>,
|
||||
#[serde(default)]
|
||||
pub display_name: String,
|
||||
pub input_properties: Vec<PropertiesRow>,
|
||||
pub output_names: Vec<String>,
|
||||
pub has_primary_output: bool,
|
||||
#[serde(default)]
|
||||
pub locked: bool,
|
||||
#[serde(default)]
|
||||
pub pinned: bool,
|
||||
pub node_type_metadata: NodeTypePersistentMetadata,
|
||||
pub network_metadata: Option<NodeNetworkMetadata>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||
pub struct PropertiesRow {
|
||||
pub input_data: HashMap<String, Value>,
|
||||
pub widget_override: Option<String>,
|
||||
#[serde(skip)]
|
||||
pub input_name: String,
|
||||
#[serde(skip)]
|
||||
pub input_description: String,
|
||||
}
|
||||
|
||||
impl From<DocumentNodePersistentMetadataPropertiesRow> for DocumentNodePersistentMetadata {
|
||||
fn from(old: DocumentNodePersistentMetadataPropertiesRow) -> Self {
|
||||
let mut input_metadata = Vec::new();
|
||||
for properties_row in old.input_properties {
|
||||
input_metadata.push(InputMetadata {
|
||||
persistent_metadata: InputPersistentMetadata {
|
||||
input_data: properties_row.input_data,
|
||||
widget_override: properties_row.widget_override,
|
||||
input_name: properties_row.input_name,
|
||||
input_description: properties_row.input_description,
|
||||
},
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
DocumentNodePersistentMetadata {
|
||||
reference: old.reference,
|
||||
display_name: old.display_name,
|
||||
input_metadata: Vec::new(),
|
||||
output_names: old.output_names,
|
||||
locked: old.locked,
|
||||
pinned: old.pinned,
|
||||
node_type_metadata: old.node_type_metadata,
|
||||
network_metadata: old.network_metadata,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Persistent metadata for each node in the network, which must be included when creating, serializing, and deserializing saving a node.
|
||||
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||
pub struct DocumentNodePersistentMetadataHasPrimaryOutput {
|
||||
pub reference: Option<String>,
|
||||
#[serde(default)]
|
||||
pub display_name: String,
|
||||
pub input_metadata: Vec<InputMetadata>,
|
||||
pub output_names: Vec<String>,
|
||||
pub has_primary_output: bool,
|
||||
#[serde(default)]
|
||||
pub locked: bool,
|
||||
#[serde(default)]
|
||||
pub pinned: bool,
|
||||
pub node_type_metadata: NodeTypePersistentMetadata,
|
||||
pub network_metadata: Option<NodeNetworkMetadata>,
|
||||
}
|
||||
|
||||
impl From<DocumentNodePersistentMetadataHasPrimaryOutput> for DocumentNodePersistentMetadata {
|
||||
fn from(old: DocumentNodePersistentMetadataHasPrimaryOutput) -> Self {
|
||||
DocumentNodePersistentMetadata {
|
||||
reference: old.reference,
|
||||
display_name: old.display_name,
|
||||
input_metadata: old.input_metadata,
|
||||
output_names: old.output_names,
|
||||
locked: old.locked,
|
||||
pinned: old.pinned,
|
||||
node_type_metadata: old.node_type_metadata,
|
||||
network_metadata: old.network_metadata,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn deserialize_node_persistent_metadata<'de, D>(deserializer: D) -> Result<DocumentNodePersistentMetadata, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
use serde::Deserialize;
|
||||
|
||||
let value = Value::deserialize(deserializer)?;
|
||||
if let Ok(document) = serde_json::from_value::<DocumentNodePersistentMetadataHasPrimaryOutput>(value.clone()) {
|
||||
return Ok(document.into());
|
||||
};
|
||||
if let Ok(document) = serde_json::from_value::<DocumentNodePersistentMetadata>(value.clone()) {
|
||||
return Ok(document);
|
||||
};
|
||||
if let Ok(document) = serde_json::from_value::<DocumentNodePersistentMetadataPropertiesRow>(value.clone()) {
|
||||
return Ok(document.into());
|
||||
};
|
||||
match serde_json::from_value::<DocumentNodePersistentMetadataInputNames>(value.clone()) {
|
||||
Ok(document) => Ok(document.into()),
|
||||
Err(e) => Err(serde::de::Error::custom(e)),
|
||||
}
|
||||
}
|
||||
@@ -61,6 +61,7 @@ pub struct LayerPanelEntry {
|
||||
pub clippable: bool,
|
||||
}
|
||||
|
||||
/// IMPORTANT: the same node may appear multiple times.
|
||||
#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize, PartialEq, Eq, specta::Type)]
|
||||
pub struct SelectedNodes(pub Vec<NodeId>);
|
||||
|
||||
@@ -129,6 +130,7 @@ impl SelectedNodes {
|
||||
self.selected_layers(metadata).any(|selected| selected == layer)
|
||||
}
|
||||
|
||||
/// IMPORTANT: the same node may appear multiple times.
|
||||
pub fn selected_nodes(&self) -> impl Iterator<Item = &NodeId> + '_ {
|
||||
self.0.iter()
|
||||
}
|
||||
|
||||
@@ -8,7 +8,8 @@ use crate::messages::tool::common_functionality::shape_editor::ShapeState;
|
||||
use crate::messages::tool::utility_types::ToolType;
|
||||
use glam::{DAffine2, DMat2, DVec2};
|
||||
use graphene_std::renderer::Quad;
|
||||
use graphene_std::vector::{HandleExt, HandleId, ManipulatorPointId, PointId, VectorModificationType};
|
||||
use graphene_std::vector::misc::{HandleId, ManipulatorPointId};
|
||||
use graphene_std::vector::{HandleExt, PointId, VectorModificationType};
|
||||
use std::collections::{HashMap, VecDeque};
|
||||
use std::f64::consts::PI;
|
||||
|
||||
@@ -79,7 +80,7 @@ impl OriginalTransforms {
|
||||
if path_map.contains_key(&layer) {
|
||||
continue;
|
||||
}
|
||||
let Some(vector_data) = network_interface.compute_modified_vector(layer) else {
|
||||
let Some(vector) = network_interface.compute_modified_vector(layer) else {
|
||||
continue;
|
||||
};
|
||||
let Some(selected_points) = shape_editor.selected_points_in_layer(layer) else {
|
||||
@@ -91,7 +92,7 @@ impl OriginalTransforms {
|
||||
|
||||
let mut selected_points = selected_points.clone();
|
||||
|
||||
for (segment_id, _, start, end) in vector_data.segment_bezier_iter() {
|
||||
for (segment_id, _, start, end) in vector.segment_bezier_iter() {
|
||||
if selected_segments.contains(&segment_id) {
|
||||
selected_points.insert(ManipulatorPointId::Anchor(start));
|
||||
selected_points.insert(ManipulatorPointId::Anchor(end));
|
||||
@@ -100,23 +101,23 @@ impl OriginalTransforms {
|
||||
|
||||
// Anchors also move their handles
|
||||
let anchor_ids = selected_points.iter().filter_map(|point| point.as_anchor());
|
||||
let anchors = anchor_ids.filter_map(|id| vector_data.point_domain.position_from_id(id).map(|pos| (id, AnchorPoint { initial: pos, current: pos })));
|
||||
let anchors = anchor_ids.filter_map(|id| vector.point_domain.position_from_id(id).map(|pos| (id, AnchorPoint { initial: pos, current: pos })));
|
||||
let anchors = anchors.collect();
|
||||
|
||||
let selected_handles = selected_points.iter().filter_map(|point| point.as_handle());
|
||||
let anchor_ids = selected_points.iter().filter_map(|point| point.as_anchor());
|
||||
let connected_handles = anchor_ids.flat_map(|point| vector_data.all_connected(point));
|
||||
let connected_handles = anchor_ids.flat_map(|point| vector.all_connected(point));
|
||||
let all_handles = selected_handles.chain(connected_handles);
|
||||
|
||||
let handles = all_handles
|
||||
.filter_map(|id| {
|
||||
let anchor = id.to_manipulator_point().get_anchor(&vector_data)?;
|
||||
let initial = id.to_manipulator_point().get_position(&vector_data)?;
|
||||
let relative = vector_data.point_domain.position_from_id(anchor)?;
|
||||
let other_handle = vector_data
|
||||
let anchor = id.to_manipulator_point().get_anchor(&vector)?;
|
||||
let initial = id.to_manipulator_point().get_position(&vector)?;
|
||||
let relative = vector.point_domain.position_from_id(anchor)?;
|
||||
let other_handle = vector
|
||||
.other_colinear_handle(id)
|
||||
.filter(|other| !selected_points.contains(&other.to_manipulator_point()) && !selected_points.contains(&ManipulatorPointId::Anchor(anchor)));
|
||||
let mirror = other_handle.and_then(|id| Some((id, id.to_manipulator_point().get_position(&vector_data)?)));
|
||||
let mirror = other_handle.and_then(|id| Some((id, id.to_manipulator_point().get_position(&vector)?)));
|
||||
|
||||
Some((id, HandlePoint { initial, relative, anchor, mirror }))
|
||||
})
|
||||
@@ -517,8 +518,8 @@ impl<'a> Selected<'a> {
|
||||
tool_type: &'a ToolType,
|
||||
pen_handle: Option<&'a mut DVec2>,
|
||||
) -> Self {
|
||||
// If user is using the Select tool then use the original layer transforms
|
||||
if (*tool_type == ToolType::Select) && (*original_transforms == OriginalTransforms::Path(HashMap::new())) {
|
||||
// If user is using the Select tool or Shape tool then use the original layer transforms
|
||||
if (*tool_type == ToolType::Select || *tool_type == ToolType::Shape) && (*original_transforms == OriginalTransforms::Path(HashMap::new())) {
|
||||
*original_transforms = OriginalTransforms::Layer(HashMap::new());
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
use crate::messages::portfolio::document::node_graph::utility_types::FrontendGraphDataType;
|
||||
use bezier_rs::{ManipulatorGroup, Subpath};
|
||||
use glam::{DVec2, IVec2};
|
||||
use graphene_std::uuid::NodeId;
|
||||
use graphene_std::vector::PointId;
|
||||
use graphene_std::{uuid::NodeId, vector::misc::dvec2_to_point};
|
||||
use kurbo::{BezPath, DEFAULT_ACCURACY, Line, Point, Shape};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
|
||||
pub struct WirePath {
|
||||
@@ -53,7 +52,7 @@ impl GraphWireStyle {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn build_vector_wire(output_position: DVec2, input_position: DVec2, vertical_out: bool, vertical_in: bool, graph_wire_style: GraphWireStyle) -> Subpath<PointId> {
|
||||
pub fn build_vector_wire(output_position: DVec2, input_position: DVec2, vertical_out: bool, vertical_in: bool, graph_wire_style: GraphWireStyle) -> BezPath {
|
||||
let grid_spacing = 24.;
|
||||
match graph_wire_style {
|
||||
GraphWireStyle::Direct => {
|
||||
@@ -85,44 +84,21 @@ pub fn build_vector_wire(output_position: DVec2, input_position: DVec2, vertical
|
||||
let delta01 = DVec2::new((locations[1].x - locations[0].x) * smoothing, (locations[1].y - locations[0].y) * smoothing);
|
||||
let delta23 = DVec2::new((locations[3].x - locations[2].x) * smoothing, (locations[3].y - locations[2].y) * smoothing);
|
||||
|
||||
Subpath::new(
|
||||
vec![
|
||||
ManipulatorGroup {
|
||||
anchor: locations[0],
|
||||
in_handle: None,
|
||||
out_handle: None,
|
||||
id: PointId::generate(),
|
||||
},
|
||||
ManipulatorGroup {
|
||||
anchor: locations[1],
|
||||
in_handle: None,
|
||||
out_handle: Some(locations[1] + delta01),
|
||||
id: PointId::generate(),
|
||||
},
|
||||
ManipulatorGroup {
|
||||
anchor: locations[2],
|
||||
in_handle: Some(locations[2] - delta23),
|
||||
out_handle: None,
|
||||
id: PointId::generate(),
|
||||
},
|
||||
ManipulatorGroup {
|
||||
anchor: locations[3],
|
||||
in_handle: None,
|
||||
out_handle: None,
|
||||
id: PointId::generate(),
|
||||
},
|
||||
],
|
||||
false,
|
||||
)
|
||||
let mut wire = BezPath::new();
|
||||
wire.move_to(dvec2_to_point(locations[0]));
|
||||
wire.line_to(dvec2_to_point(locations[1]));
|
||||
wire.curve_to(dvec2_to_point(locations[1] + delta01), dvec2_to_point(locations[2] - delta23), dvec2_to_point(locations[2]));
|
||||
wire.line_to(dvec2_to_point(locations[3]));
|
||||
wire
|
||||
}
|
||||
GraphWireStyle::GridAligned => {
|
||||
let locations = straight_wire_paths(output_position, input_position, vertical_out, vertical_in);
|
||||
straight_wire_subpath(locations)
|
||||
let locations = straight_wire_path(output_position, input_position, vertical_out, vertical_in);
|
||||
straight_wire_to_bezpath(locations)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn straight_wire_paths(output_position: DVec2, input_position: DVec2, vertical_out: bool, vertical_in: bool) -> Vec<IVec2> {
|
||||
fn straight_wire_path(output_position: DVec2, input_position: DVec2, vertical_out: bool, vertical_in: bool) -> Vec<IVec2> {
|
||||
let grid_spacing = 24;
|
||||
let line_width = 2;
|
||||
|
||||
@@ -446,40 +422,24 @@ fn straight_wire_paths(output_position: DVec2, input_position: DVec2, vertical_o
|
||||
vec![IVec2::new(x1, y1), IVec2::new(x20, y1), IVec2::new(x20, y3), IVec2::new(x4, y3)]
|
||||
}
|
||||
|
||||
fn straight_wire_subpath(locations: Vec<IVec2>) -> Subpath<PointId> {
|
||||
fn straight_wire_to_bezpath(locations: Vec<IVec2>) -> BezPath {
|
||||
if locations.is_empty() {
|
||||
return Subpath::new(Vec::new(), false);
|
||||
return BezPath::new();
|
||||
}
|
||||
|
||||
let to_point = |location: IVec2| Point::new(location.x as f64, location.y as f64);
|
||||
|
||||
if locations.len() == 2 {
|
||||
return Subpath::new(
|
||||
vec![
|
||||
ManipulatorGroup {
|
||||
anchor: locations[0].into(),
|
||||
in_handle: None,
|
||||
out_handle: None,
|
||||
id: PointId::generate(),
|
||||
},
|
||||
ManipulatorGroup {
|
||||
anchor: locations[1].into(),
|
||||
in_handle: None,
|
||||
out_handle: None,
|
||||
id: PointId::generate(),
|
||||
},
|
||||
],
|
||||
false,
|
||||
);
|
||||
let p1 = to_point(locations[0]);
|
||||
let p2 = to_point(locations[1]);
|
||||
Line::new(p1, p2).to_path(DEFAULT_ACCURACY);
|
||||
}
|
||||
|
||||
let corner_radius = 10;
|
||||
|
||||
// Create path with rounded corners
|
||||
let mut path = vec![ManipulatorGroup {
|
||||
anchor: locations[0].into(),
|
||||
in_handle: None,
|
||||
out_handle: None,
|
||||
id: PointId::generate(),
|
||||
}];
|
||||
let mut path = BezPath::new();
|
||||
path.move_to(to_point(locations[0]));
|
||||
|
||||
for i in 1..(locations.len() - 1) {
|
||||
let prev = locations[i - 1];
|
||||
@@ -563,27 +523,9 @@ fn straight_wire_subpath(locations: Vec<IVec2>) -> Subpath<PointId> {
|
||||
},
|
||||
);
|
||||
|
||||
path.extend(vec![
|
||||
ManipulatorGroup {
|
||||
anchor: corner_start.into(),
|
||||
in_handle: None,
|
||||
out_handle: Some(corner_start_mid.into()),
|
||||
id: PointId::generate(),
|
||||
},
|
||||
ManipulatorGroup {
|
||||
anchor: corner_end.into(),
|
||||
in_handle: Some(corner_end_mid.into()),
|
||||
out_handle: None,
|
||||
id: PointId::generate(),
|
||||
},
|
||||
])
|
||||
path.line_to(to_point(corner_start));
|
||||
path.curve_to(to_point(corner_start_mid), to_point(corner_end_mid), to_point(corner_end));
|
||||
}
|
||||
|
||||
path.push(ManipulatorGroup {
|
||||
anchor: (*locations.last().unwrap()).into(),
|
||||
in_handle: None,
|
||||
out_handle: None,
|
||||
id: PointId::generate(),
|
||||
});
|
||||
Subpath::new(path, false)
|
||||
path.line_to(to_point(*locations.last().unwrap()));
|
||||
path
|
||||
}
|
||||
|
||||
@@ -5,20 +5,22 @@ use crate::messages::portfolio::document::node_graph::document_node_definitions:
|
||||
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
||||
use crate::messages::portfolio::document::utility_types::network_interface::{InputConnector, NodeTemplate, OutputConnector};
|
||||
use crate::messages::prelude::DocumentMessageHandler;
|
||||
use bezier_rs::Subpath;
|
||||
use glam::IVec2;
|
||||
use graph_craft::document::DocumentNode;
|
||||
use graph_craft::document::{DocumentNodeImplementation, NodeInput, value::TaggedValue};
|
||||
use graphene_std::ProtoNodeIdentifier;
|
||||
use graphene_std::text::TypesettingConfig;
|
||||
use graphene_std::subpath::Subpath;
|
||||
use graphene_std::table::Table;
|
||||
use graphene_std::text::{TextAlign, TypesettingConfig};
|
||||
use graphene_std::uuid::NodeId;
|
||||
use graphene_std::vector::Vector;
|
||||
use graphene_std::vector::style::{PaintOrder, StrokeAlign};
|
||||
use graphene_std::vector::{VectorData, VectorDataTable};
|
||||
use std::collections::HashMap;
|
||||
|
||||
const TEXT_REPLACEMENTS: &[(&str, &str)] = &[
|
||||
("graphene_core::vector::vector_nodes::SamplePointsNode", "graphene_core::vector::SamplePolylineNode"),
|
||||
("graphene_core::vector::vector_nodes::SubpathSegmentLengthsNode", "graphene_core::vector::SubpathSegmentLengthsNode"),
|
||||
("\"manual_composition\":null", "\"manual_composition\":{\"Generic\":\"T\"}"),
|
||||
];
|
||||
|
||||
pub struct NodeReplacement<'a> {
|
||||
@@ -27,22 +29,56 @@ pub struct NodeReplacement<'a> {
|
||||
}
|
||||
|
||||
const NODE_REPLACEMENTS: &[NodeReplacement<'static>] = &[
|
||||
// graphic element
|
||||
// artboard
|
||||
NodeReplacement {
|
||||
node: graphene_std::graphic_element::append_artboard::IDENTIFIER,
|
||||
aliases: &["graphene_core::AddArtboardNode"],
|
||||
node: graphene_std::artboard::create_artboard::IDENTIFIER,
|
||||
aliases: &[
|
||||
"graphene_core::ConstructArtboardNode",
|
||||
"graphene_core::graphic_element::ToArtboardNode",
|
||||
"graphene_core::artboard::ToArtboardNode",
|
||||
],
|
||||
},
|
||||
// graphic
|
||||
NodeReplacement {
|
||||
node: graphene_std::graphic::to_graphic::IDENTIFIER,
|
||||
aliases: &[
|
||||
"graphene_core::ToGraphicGroupNode",
|
||||
"graphene_core::graphic_element::ToGroupNode",
|
||||
"graphene_core::graphic::ToGroupNode",
|
||||
],
|
||||
},
|
||||
NodeReplacement {
|
||||
node: graphene_std::graphic_element::to_artboard::IDENTIFIER,
|
||||
aliases: &["graphene_core::ConstructArtboardNode"],
|
||||
node: graphene_std::graphic::wrap_graphic::IDENTIFIER,
|
||||
aliases: &[
|
||||
// Converted from "To Element"
|
||||
"graphene_core::ToGraphicElementNode",
|
||||
"graphene_core::graphic_element::ToElementNode",
|
||||
"graphene_core::graphic::ToElementNode",
|
||||
],
|
||||
},
|
||||
NodeReplacement {
|
||||
node: graphene_std::graphic_element::to_element::IDENTIFIER,
|
||||
aliases: &["graphene_core::ToGraphicElementNode"],
|
||||
node: graphene_std::graphic::legacy_layer_extend::IDENTIFIER,
|
||||
aliases: &[
|
||||
"graphene_core::graphic_element::LayerNode",
|
||||
"graphene_core::graphic::LayerNode",
|
||||
// Converted from "Append Artboard"
|
||||
"graphene_core::AddArtboardNode",
|
||||
"graphene_core::graphic_element::AppendArtboardNode",
|
||||
"graphene_core::graphic::AppendArtboardNode",
|
||||
"graphene_core::artboard::AppendArtboardNode",
|
||||
],
|
||||
},
|
||||
NodeReplacement {
|
||||
node: graphene_std::graphic_element::to_group::IDENTIFIER,
|
||||
aliases: &["graphene_core::ToGraphicGroupNode"],
|
||||
node: graphene_std::graphic::flatten_graphic::IDENTIFIER,
|
||||
aliases: &["graphene_core::graphic_element::FlattenGroupNode", "graphene_core::graphic::FlattenGroupNode"],
|
||||
},
|
||||
NodeReplacement {
|
||||
node: graphene_std::graphic::flatten_vector::IDENTIFIER,
|
||||
aliases: &["graphene_core::graphic_element::FlattenVectorNode"],
|
||||
},
|
||||
NodeReplacement {
|
||||
node: graphene_std::graphic::index::IDENTIFIER,
|
||||
aliases: &["graphene_core::graphic_element::IndexNode"],
|
||||
},
|
||||
// math_nodes
|
||||
NodeReplacement {
|
||||
@@ -186,13 +222,26 @@ const NODE_REPLACEMENTS: &[NodeReplacement<'static>] = &[
|
||||
aliases: &["graphene_core::ops::PercentageValueNode"],
|
||||
},
|
||||
NodeReplacement {
|
||||
node: graphene_std::math_nodes::coordinate_value::IDENTIFIER,
|
||||
node: graphene_std::math_nodes::vec_2_value::IDENTIFIER,
|
||||
aliases: &[
|
||||
"graphene_core::ops::CoordinateValueNode",
|
||||
"graphene_core::ops::ConstructVector2",
|
||||
"graphene_core::ops::Vector2ValueNode",
|
||||
"graphene_core::ops::CoordinateValueNode",
|
||||
"graphene_math_nodes::CoordinateValueNode",
|
||||
],
|
||||
},
|
||||
NodeReplacement {
|
||||
node: graphene_std::vector::cut_segments::IDENTIFIER,
|
||||
aliases: &["graphene_core::vector::SplitSegmentsNode"],
|
||||
},
|
||||
NodeReplacement {
|
||||
node: graphene_std::vector::cut_path::IDENTIFIER,
|
||||
aliases: &["graphene_core::vector::SplitPathNode"],
|
||||
},
|
||||
NodeReplacement {
|
||||
node: graphene_std::vector::vec_2_to_point::IDENTIFIER,
|
||||
aliases: &["graphene_core::vector::PositionToPointNode"],
|
||||
},
|
||||
NodeReplacement {
|
||||
node: graphene_std::math_nodes::color_value::IDENTIFIER,
|
||||
aliases: &["graphene_core::ops::ColorValueNode"],
|
||||
@@ -223,8 +272,8 @@ const NODE_REPLACEMENTS: &[NodeReplacement<'static>] = &[
|
||||
aliases: &["graphene_core::ops::SomeNode"],
|
||||
},
|
||||
NodeReplacement {
|
||||
node: graphene_std::debug::unwrap::IDENTIFIER,
|
||||
aliases: &["graphene_core::ops::UnwrapNode"],
|
||||
node: graphene_std::debug::unwrap_option::IDENTIFIER,
|
||||
aliases: &["graphene_core::ops::UnwrapNode", "graphene_core::debug::UnwrapNode"],
|
||||
},
|
||||
NodeReplacement {
|
||||
node: graphene_std::debug::clone::IDENTIFIER,
|
||||
@@ -251,7 +300,24 @@ const NODE_REPLACEMENTS: &[NodeReplacement<'static>] = &[
|
||||
node: graphene_std::vector::auto_tangents::IDENTIFIER,
|
||||
aliases: &["graphene_core::vector::GenerateHandlesNode", "graphene_core::vector::RemoveHandlesNode"],
|
||||
},
|
||||
// raster::adjustments
|
||||
// graphene_raster_nodes::blending_nodes
|
||||
NodeReplacement {
|
||||
node: graphene_std::raster_nodes::blending_nodes::blend::IDENTIFIER,
|
||||
aliases: &[
|
||||
"graphene_raster_nodes::adjustments::BlendNode",
|
||||
"graphene_core::raster::adjustments::BlendNode",
|
||||
"graphene_core::raster::BlendNode",
|
||||
],
|
||||
},
|
||||
NodeReplacement {
|
||||
node: graphene_std::raster_nodes::blending_nodes::color_overlay::IDENTIFIER,
|
||||
aliases: &[
|
||||
"graphene_raster_nodes::adjustments::ColorOverlayNode",
|
||||
"graphene_core::raster::adjustments::ColorOverlayNode",
|
||||
"graphene_raster_nodes::generate_curves::ColorOverlayNode",
|
||||
],
|
||||
},
|
||||
// graphene_raster_nodes::adjustments
|
||||
NodeReplacement {
|
||||
node: graphene_std::raster_nodes::adjustments::luminance::IDENTIFIER,
|
||||
aliases: &["graphene_core::raster::adjustments::LuminanceNode", "graphene_core::raster::LuminanceNode"],
|
||||
@@ -292,20 +358,6 @@ const NODE_REPLACEMENTS: &[NodeReplacement<'static>] = &[
|
||||
node: graphene_std::raster_nodes::adjustments::threshold::IDENTIFIER,
|
||||
aliases: &["graphene_core::raster::adjustments::ThresholdNode", "graphene_core::raster::ThresholdNode"],
|
||||
},
|
||||
NodeReplacement {
|
||||
node: graphene_std::raster_nodes::adjustments::blend::IDENTIFIER,
|
||||
aliases: &["graphene_core::raster::adjustments::BlendNode", "graphene_core::raster::BlendNode"],
|
||||
},
|
||||
NodeReplacement {
|
||||
node: graphene_std::raster_nodes::adjustments::blend_color_pair::IDENTIFIER,
|
||||
aliases: &["graphene_core::raster::BlendColorPairNode"],
|
||||
},
|
||||
// this node doesn't seem to exist?
|
||||
// (graphene_std::raster_nodes::adjustments::blend_color::IDENTIFIER, &["graphene_core::raster::adjustments::BlendColorsNode","graphene_core::raster::BlendColorsNode"]),
|
||||
NodeReplacement {
|
||||
node: graphene_std::raster_nodes::adjustments::gradient_map::IDENTIFIER,
|
||||
aliases: &["graphene_core::raster::adjustments::GradientMapNode", "graphene_core::raster::GradientMapNode"],
|
||||
},
|
||||
NodeReplacement {
|
||||
node: graphene_std::raster_nodes::adjustments::vibrance::IDENTIFIER,
|
||||
aliases: &["graphene_core::raster::adjustments::VibranceNode", "graphene_core::raster::VibranceNode"],
|
||||
@@ -326,11 +378,16 @@ const NODE_REPLACEMENTS: &[NodeReplacement<'static>] = &[
|
||||
node: graphene_std::raster_nodes::adjustments::exposure::IDENTIFIER,
|
||||
aliases: &["graphene_core::raster::adjustments::ExposureNode", "graphene_core::raster::ExposureNode"],
|
||||
},
|
||||
// graphene_raster_nodes::*
|
||||
NodeReplacement {
|
||||
node: graphene_std::raster_nodes::adjustments::color_overlay::IDENTIFIER,
|
||||
aliases: &["graphene_core::raster::adjustments::ColorOverlayNode", "graphene_raster_nodes::generate_curves::ColorOverlayNode"],
|
||||
node: graphene_std::raster_nodes::gradient_map::gradient_map::IDENTIFIER,
|
||||
aliases: &[
|
||||
"graphene_raster_nodes::gradient_map::GradientMapNode",
|
||||
"graphene_raster_nodes::adjustments::GradientMapNode",
|
||||
"graphene_core::raster::adjustments::GradientMapNode",
|
||||
"graphene_core::raster::GradientMapNode",
|
||||
],
|
||||
},
|
||||
// raster
|
||||
NodeReplacement {
|
||||
node: graphene_std::raster_nodes::generate_curves::generate_curves::IDENTIFIER,
|
||||
aliases: &["graphene_core::raster::adjustments::GenerateCurvesNode"],
|
||||
@@ -369,7 +426,7 @@ const NODE_REPLACEMENTS: &[NodeReplacement<'static>] = &[
|
||||
},
|
||||
NodeReplacement {
|
||||
node: graphene_std::raster_nodes::std_nodes::image_value::IDENTIFIER,
|
||||
aliases: &["graphene_std::raster::ImageValueNode"],
|
||||
aliases: &["graphene_std::raster::ImageValueNode", "graphene_std::raster::ImageNode"],
|
||||
},
|
||||
NodeReplacement {
|
||||
node: graphene_std::raster_nodes::std_nodes::noise_pattern::IDENTIFIER,
|
||||
@@ -438,6 +495,10 @@ const NODE_REPLACEMENTS: &[NodeReplacement<'static>] = &[
|
||||
node: graphene_std::path_bool::boolean_operation::IDENTIFIER,
|
||||
aliases: &["graphene_std::vector::BooleanOperationNode"],
|
||||
},
|
||||
NodeReplacement {
|
||||
node: graphene_std::vector::path_modify::IDENTIFIER,
|
||||
aliases: &["graphene_core::vector::vector_data::modification::PathModifyNode"],
|
||||
},
|
||||
// brush
|
||||
NodeReplacement {
|
||||
node: graphene_std::brush::brush::brush_stamp_generator::IDENTIFIER,
|
||||
@@ -499,7 +560,7 @@ pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_
|
||||
let mut default_template = NodeTemplate::default();
|
||||
default_template.document_node.implementation = DocumentNodeImplementation::ProtoNode(new.clone());
|
||||
document.network_interface.replace_implementation(node_id, &network_path, &mut default_template);
|
||||
document.network_interface.set_manual_compostion(node_id, &network_path, Some(graph_craft::Type::Generic("T".into())));
|
||||
document.network_interface.set_call_argument(node_id, &network_path, graph_craft::Type::Generic("T".into()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -524,11 +585,11 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId],
|
||||
}
|
||||
}
|
||||
|
||||
// Upgrade old nodes to use `Context` instead of `()` or `Footprint` for manual composition
|
||||
if node.manual_composition == Some(graph_craft::concrete!(())) || node.manual_composition == Some(graph_craft::concrete!(graphene_std::transform::Footprint)) {
|
||||
// Upgrade old nodes to use `Context` instead of `()` or `Footprint` as their call argument
|
||||
if node.call_argument == graph_craft::concrete!(()) || node.call_argument == graph_craft::concrete!(graphene_std::transform::Footprint) {
|
||||
document
|
||||
.network_interface
|
||||
.set_manual_compostion(node_id, network_path, graph_craft::concrete!(graphene_std::Context).into());
|
||||
.set_call_argument(node_id, network_path, graph_craft::concrete!(graphene_std::Context).into());
|
||||
}
|
||||
|
||||
// Only nodes that have not been modified and still refer to a definition can be updated
|
||||
@@ -573,17 +634,17 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId],
|
||||
return None;
|
||||
}
|
||||
|
||||
// Obtain the document node for the given node ID, extract the vector points, and create vector data from the list of points
|
||||
// Obtain the document node for the given node ID, extract the vector points, and create a Vector path from the list of points
|
||||
let node = document.network_interface.document_node(node_id, network_path)?;
|
||||
let Some(TaggedValue::VecDVec2(points)) = node.inputs.get(1).and_then(|tagged_value| tagged_value.as_value()) else {
|
||||
log::error!("The old Spline node's input at index 1 is not a TaggedValue::VecDVec2");
|
||||
return None;
|
||||
};
|
||||
let vector_data = VectorData::from_subpath(Subpath::from_anchors_linear(points.to_vec(), false));
|
||||
let vector = Vector::from_subpath(Subpath::from_anchors_linear(points.to_vec(), false));
|
||||
|
||||
// Retrieve the output connectors linked to the "Spline" node's output port
|
||||
// Retrieve the output connectors linked to the "Spline" node's output connector
|
||||
let Some(spline_outputs) = document.network_interface.outward_wires(network_path)?.get(&OutputConnector::node(*node_id, 0)).cloned() else {
|
||||
log::error!("Vec of InputConnector Spline node is connected to its output port 0.");
|
||||
log::error!("Vec of InputConnector Spline node is connected to its output connector 0.");
|
||||
return None;
|
||||
};
|
||||
|
||||
@@ -593,13 +654,13 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId],
|
||||
return None;
|
||||
};
|
||||
|
||||
// Get the "Path" node definition and fill it in with the vector data and default vector modification
|
||||
// Get the "Path" node definition and fill it in with the Vector path and default vector modification
|
||||
let Some(path_node_type) = resolve_document_node_type("Path") else {
|
||||
log::error!("Path node does not exist.");
|
||||
return None;
|
||||
};
|
||||
let path_node = path_node_type.node_template_input_override([
|
||||
Some(NodeInput::value(TaggedValue::VectorData(VectorDataTable::new(vector_data)), true)),
|
||||
Some(NodeInput::value(TaggedValue::Vector(Table::new_from_element(vector)), true)),
|
||||
Some(NodeInput::value(TaggedValue::VectorModification(Default::default()), false)),
|
||||
]);
|
||||
|
||||
@@ -628,14 +689,14 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId],
|
||||
// Reposition the new "Path" node with an offset relative to the original "Spline" node's position
|
||||
document.network_interface.shift_node(&new_path_id, node_position + IVec2::new(-7, 0), network_path);
|
||||
|
||||
// Redirect each output connection from the old node to the new "Spline" node's output port
|
||||
// Redirect each output connection from the old node to the new "Spline" node's output connector
|
||||
for input_connector in spline_outputs {
|
||||
document.network_interface.set_input(&input_connector, NodeInput::node(new_spline_id, 0), network_path);
|
||||
}
|
||||
}
|
||||
|
||||
// Upgrade Text node to include line height and character spacing, which were previously hardcoded to 1, from https://github.com/GraphiteEditor/Graphite/pull/2016
|
||||
if reference == "Text" && inputs_count != 10 {
|
||||
if reference == "Text" && inputs_count != 11 {
|
||||
let mut template = resolve_document_node_type(reference)?.default_node_template();
|
||||
document.network_interface.replace_implementation(node_id, network_path, &mut template);
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut template)?;
|
||||
@@ -691,8 +752,17 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId],
|
||||
);
|
||||
document.network_interface.set_input(
|
||||
&InputConnector::node(*node_id, 9),
|
||||
if inputs_count >= 10 {
|
||||
if inputs_count >= 11 {
|
||||
old_inputs[9].clone()
|
||||
} else {
|
||||
NodeInput::value(TaggedValue::TextAlign(TextAlign::default()), false)
|
||||
},
|
||||
network_path,
|
||||
);
|
||||
document.network_interface.set_input(
|
||||
&InputConnector::node(*node_id, 10),
|
||||
if inputs_count >= 11 {
|
||||
old_inputs[10].clone()
|
||||
} else {
|
||||
NodeInput::value(TaggedValue::Bool(false), false)
|
||||
},
|
||||
@@ -765,7 +835,7 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId],
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 4), old_inputs[3].clone(), network_path);
|
||||
}
|
||||
|
||||
// Upgrade artboard name being passed as hidden value input to "To Artboard"
|
||||
// Upgrade artboard name being passed as hidden value input to "Create Artboard"
|
||||
if reference == "Artboard" && reset_node_definitions_on_open {
|
||||
let label = document.network_interface.display_name(node_id, network_path);
|
||||
document
|
||||
@@ -1022,8 +1092,8 @@ mod tests {
|
||||
*hashmap.entry(node.node.clone()).or_default() += 1;
|
||||
});
|
||||
let duplicates = hashmap.iter().filter(|(_, count)| **count > 1).map(|(node, _)| &node.name).collect::<Vec<_>>();
|
||||
if duplicates.len() > 0 {
|
||||
panic!("Duplicate entries in `NODE_REPLACEMENTS`: {:?}", duplicates);
|
||||
if !duplicates.is_empty() {
|
||||
panic!("Duplicate entries in `NODE_REPLACEMENTS`: {duplicates:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,10 +16,12 @@ pub struct MenuBarMessageHandler {
|
||||
pub has_selected_nodes: bool,
|
||||
pub has_selected_layers: bool,
|
||||
pub has_selection_history: (bool, bool),
|
||||
pub spreadsheet_view_open: bool,
|
||||
pub message_logging_verbosity: MessageLoggingVerbosity,
|
||||
pub reset_node_definitions_on_open: bool,
|
||||
pub single_path_node_compatible_layer_selected: bool,
|
||||
pub make_path_editable_is_allowed: bool,
|
||||
pub data_panel_open: bool,
|
||||
pub layers_panel_open: bool,
|
||||
pub properties_panel_open: bool,
|
||||
}
|
||||
|
||||
#[message_handler_data]
|
||||
@@ -46,7 +48,7 @@ impl LayoutHolder for MenuBarMessageHandler {
|
||||
let message_logging_verbosity_names = self.message_logging_verbosity == MessageLoggingVerbosity::Names;
|
||||
let message_logging_verbosity_contents = self.message_logging_verbosity == MessageLoggingVerbosity::Contents;
|
||||
let reset_node_definitions_on_open = self.reset_node_definitions_on_open;
|
||||
let single_path_node_compatible_layer_selected = self.single_path_node_compatible_layer_selected;
|
||||
let make_path_editable_is_allowed = self.make_path_editable_is_allowed;
|
||||
|
||||
let menu_bar_entries = vec![
|
||||
MenuBarEntry {
|
||||
@@ -99,14 +101,25 @@ impl LayoutHolder for MenuBarMessageHandler {
|
||||
..MenuBarEntry::default()
|
||||
},
|
||||
],
|
||||
vec![MenuBarEntry {
|
||||
label: "Save".into(),
|
||||
icon: Some("Save".into()),
|
||||
shortcut: action_keys!(DocumentMessageDiscriminant::SaveDocument),
|
||||
action: MenuBarEntry::create_action(|_| DocumentMessage::SaveDocument.into()),
|
||||
disabled: no_active_document,
|
||||
..MenuBarEntry::default()
|
||||
}],
|
||||
vec![
|
||||
MenuBarEntry {
|
||||
label: "Save".into(),
|
||||
icon: Some("Save".into()),
|
||||
shortcut: action_keys!(DocumentMessageDiscriminant::SaveDocument),
|
||||
action: MenuBarEntry::create_action(|_| DocumentMessage::SaveDocument.into()),
|
||||
disabled: no_active_document,
|
||||
..MenuBarEntry::default()
|
||||
},
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
MenuBarEntry {
|
||||
label: "Save As…".into(),
|
||||
icon: Some("Save".into()),
|
||||
shortcut: action_keys!(DocumentMessageDiscriminant::SaveDocumentAs),
|
||||
action: MenuBarEntry::create_action(|_| DocumentMessage::SaveDocumentAs.into()),
|
||||
disabled: no_active_document,
|
||||
..MenuBarEntry::default()
|
||||
},
|
||||
],
|
||||
vec![
|
||||
MenuBarEntry {
|
||||
label: "Import…".into(),
|
||||
@@ -359,8 +372,8 @@ impl LayoutHolder for MenuBarMessageHandler {
|
||||
|
||||
choices
|
||||
.into_iter()
|
||||
.map(|group| {
|
||||
group
|
||||
.map(|section| {
|
||||
section
|
||||
.into_iter()
|
||||
.map(|(axis, aggregate, icon, name)| MenuBarEntry {
|
||||
label: name.into(),
|
||||
@@ -419,7 +432,7 @@ impl LayoutHolder for MenuBarMessageHandler {
|
||||
action: MenuBarEntry::no_action(),
|
||||
disabled: no_active_document || !has_selected_layers,
|
||||
children: MenuBarEntryChildren(vec![{
|
||||
let list = <BooleanOperation as graphene_std::registry::ChoiceTypeStatic>::list();
|
||||
let list = <BooleanOperation as graphene_std::choice_type::ChoiceTypeStatic>::list();
|
||||
list.iter()
|
||||
.flat_map(|i| i.iter())
|
||||
.map(move |(operation, info)| MenuBarEntry {
|
||||
@@ -442,7 +455,7 @@ impl LayoutHolder for MenuBarMessageHandler {
|
||||
icon: Some("NodeShape".into()),
|
||||
shortcut: None,
|
||||
action: MenuBarEntry::create_action(|_| NodeGraphMessage::AddPathNode.into()),
|
||||
disabled: !single_path_node_compatible_layer_selected,
|
||||
disabled: !make_path_editable_is_allowed,
|
||||
..MenuBarEntry::default()
|
||||
}],
|
||||
]),
|
||||
@@ -585,18 +598,40 @@ impl LayoutHolder for MenuBarMessageHandler {
|
||||
disabled: no_active_document,
|
||||
..MenuBarEntry::default()
|
||||
}],
|
||||
]),
|
||||
),
|
||||
MenuBarEntry::new_root(
|
||||
"Window".into(),
|
||||
false,
|
||||
MenuBarEntryChildren(vec![
|
||||
vec![
|
||||
MenuBarEntry {
|
||||
label: "Properties".into(),
|
||||
icon: Some(if self.properties_panel_open { "CheckboxChecked" } else { "CheckboxUnchecked" }.into()),
|
||||
shortcut: action_keys!(PortfolioMessageDiscriminant::TogglePropertiesPanelOpen),
|
||||
action: MenuBarEntry::create_action(|_| PortfolioMessage::TogglePropertiesPanelOpen.into()),
|
||||
..MenuBarEntry::default()
|
||||
},
|
||||
MenuBarEntry {
|
||||
label: "Layers".into(),
|
||||
icon: Some(if self.layers_panel_open { "CheckboxChecked" } else { "CheckboxUnchecked" }.into()),
|
||||
shortcut: action_keys!(PortfolioMessageDiscriminant::ToggleLayersPanelOpen),
|
||||
action: MenuBarEntry::create_action(|_| PortfolioMessage::ToggleLayersPanelOpen.into()),
|
||||
..MenuBarEntry::default()
|
||||
},
|
||||
],
|
||||
vec![MenuBarEntry {
|
||||
label: "Window: Spreadsheet".into(),
|
||||
icon: Some(if self.spreadsheet_view_open { "CheckboxChecked" } else { "CheckboxUnchecked" }.into()),
|
||||
action: MenuBarEntry::create_action(|_| SpreadsheetMessage::ToggleOpen.into()),
|
||||
disabled: no_active_document,
|
||||
label: "Data".into(),
|
||||
icon: Some(if self.data_panel_open { "CheckboxChecked" } else { "CheckboxUnchecked" }.into()),
|
||||
shortcut: action_keys!(PortfolioMessageDiscriminant::ToggleDataPanelOpen),
|
||||
action: MenuBarEntry::create_action(|_| PortfolioMessage::ToggleDataPanelOpen.into()),
|
||||
..MenuBarEntry::default()
|
||||
}],
|
||||
]),
|
||||
),
|
||||
MenuBarEntry::new_root(
|
||||
"Help".into(),
|
||||
true,
|
||||
false,
|
||||
MenuBarEntryChildren(vec![
|
||||
vec![MenuBarEntry {
|
||||
label: "About Graphite…".into(),
|
||||
|
||||
@@ -4,7 +4,6 @@ mod portfolio_message_handler;
|
||||
pub mod document;
|
||||
pub mod document_migration;
|
||||
pub mod menu_bar;
|
||||
pub mod spreadsheet;
|
||||
pub mod utility_types;
|
||||
|
||||
#[doc(inline)]
|
||||
|
||||
@@ -6,6 +6,7 @@ use crate::messages::prelude::*;
|
||||
use graphene_std::Color;
|
||||
use graphene_std::raster::Image;
|
||||
use graphene_std::text::Font;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[impl_message(Message, Portfolio)]
|
||||
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
|
||||
@@ -15,8 +16,6 @@ pub enum PortfolioMessage {
|
||||
MenuBar(MenuBarMessage),
|
||||
#[child]
|
||||
Document(DocumentMessage),
|
||||
#[child]
|
||||
Spreadsheet(SpreadsheetMessage),
|
||||
|
||||
// Messages
|
||||
Init,
|
||||
@@ -68,18 +67,20 @@ pub enum PortfolioMessage {
|
||||
NextDocument,
|
||||
OpenDocument,
|
||||
OpenDocumentFile {
|
||||
document_name: String,
|
||||
document_name: Option<String>,
|
||||
document_path: Option<PathBuf>,
|
||||
document_serialized_content: String,
|
||||
},
|
||||
ToggleResetNodesToDefinitionsOnOpen,
|
||||
OpenDocumentFileWithId {
|
||||
document_id: DocumentId,
|
||||
document_name: String,
|
||||
document_name: Option<String>,
|
||||
document_path: Option<PathBuf>,
|
||||
document_is_auto_saved: bool,
|
||||
document_is_saved: bool,
|
||||
document_serialized_content: String,
|
||||
to_front: bool,
|
||||
},
|
||||
ToggleResetNodesToDefinitionsOnOpen,
|
||||
PasteIntoFolder {
|
||||
clipboard: Clipboard,
|
||||
parent: LayerNodeIdentifier,
|
||||
@@ -88,6 +89,9 @@ pub enum PortfolioMessage {
|
||||
PasteSerializedData {
|
||||
data: String,
|
||||
},
|
||||
PasteSerializedVector {
|
||||
data: String,
|
||||
},
|
||||
CenterPastedLayers {
|
||||
layers: Vec<LayerNodeIdentifier>,
|
||||
},
|
||||
@@ -114,7 +118,7 @@ pub enum PortfolioMessage {
|
||||
document_id: DocumentId,
|
||||
},
|
||||
SubmitDocumentExport {
|
||||
file_name: String,
|
||||
name: String,
|
||||
file_type: FileType,
|
||||
scale_factor: f64,
|
||||
bounds: ExportBounds,
|
||||
@@ -125,6 +129,9 @@ pub enum PortfolioMessage {
|
||||
document_id: DocumentId,
|
||||
ignore_hash: bool,
|
||||
},
|
||||
ToggleDataPanelOpen,
|
||||
TogglePropertiesPanelOpen,
|
||||
ToggleLayersPanelOpen,
|
||||
ToggleRulers,
|
||||
UpdateDocumentWidgets,
|
||||
UpdateOpenDocumentsList,
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
use super::document::utility_types::document_metadata::LayerNodeIdentifier;
|
||||
use super::document::utility_types::network_interface;
|
||||
use super::spreadsheet::SpreadsheetMessageHandler;
|
||||
use super::utility_types::{PanelType, PersistentData};
|
||||
use crate::application::generate_uuid;
|
||||
use crate::consts::DEFAULT_DOCUMENT_NAME;
|
||||
use crate::consts::{DEFAULT_DOCUMENT_NAME, DEFAULT_STROKE_WIDTH, FILE_EXTENSION};
|
||||
use crate::messages::animation::TimingInformation;
|
||||
use crate::messages::debug::utility_types::MessageLoggingVerbosity;
|
||||
use crate::messages::dialog::simple_dialogs;
|
||||
@@ -12,6 +11,7 @@ use crate::messages::layout::utility_types::widget_prelude::*;
|
||||
use crate::messages::portfolio::document::DocumentMessageContext;
|
||||
use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn;
|
||||
use crate::messages::portfolio::document::node_graph::document_node_definitions;
|
||||
use crate::messages::portfolio::document::node_graph::document_node_definitions::resolve_document_node_type;
|
||||
use crate::messages::portfolio::document::utility_types::clipboards::{Clipboard, CopyBufferEntry, INTERNAL_CLIPBOARD_COUNT};
|
||||
use crate::messages::portfolio::document::utility_types::network_interface::OutputConnector;
|
||||
use crate::messages::portfolio::document::utility_types::nodes::SelectedNodes;
|
||||
@@ -19,27 +19,33 @@ use crate::messages::portfolio::document_migration::*;
|
||||
use crate::messages::preferences::SelectionMode;
|
||||
use crate::messages::prelude::*;
|
||||
use crate::messages::tool::common_functionality::graph_modification_utils;
|
||||
use crate::messages::tool::common_functionality::utility_functions::make_path_editable_is_allowed;
|
||||
use crate::messages::tool::utility_types::{HintData, HintGroup, ToolType};
|
||||
use crate::node_graph_executor::{ExportConfig, NodeGraphExecutor};
|
||||
use derivative::*;
|
||||
use glam::{DAffine2, DVec2};
|
||||
use graph_craft::document::NodeId;
|
||||
use graph_craft::document::value::TaggedValue;
|
||||
use graphene_std::Color;
|
||||
use graphene_std::renderer::Quad;
|
||||
use graphene_std::subpath::BezierHandles;
|
||||
use graphene_std::text::Font;
|
||||
use graphene_std::vector::misc::HandleId;
|
||||
use graphene_std::vector::{PointId, SegmentId, Vector, VectorModificationType};
|
||||
use std::vec;
|
||||
|
||||
#[derive(ExtractField)]
|
||||
pub struct PortfolioMessageContext<'a> {
|
||||
pub ipp: &'a InputPreprocessorMessageHandler,
|
||||
pub preferences: &'a PreferencesMessageHandler,
|
||||
pub animation: &'a AnimationMessageHandler,
|
||||
pub current_tool: &'a ToolType,
|
||||
pub message_logging_verbosity: MessageLoggingVerbosity,
|
||||
pub reset_node_definitions_on_open: bool,
|
||||
pub timing_information: TimingInformation,
|
||||
pub animation: &'a AnimationMessageHandler,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, ExtractField)]
|
||||
#[derive(Debug, Derivative, ExtractField)]
|
||||
#[derivative(Default)]
|
||||
pub struct PortfolioMessageHandler {
|
||||
menu_bar_message_handler: MenuBarMessageHandler,
|
||||
pub documents: HashMap<DocumentId, DocumentMessageHandler>,
|
||||
@@ -50,10 +56,13 @@ pub struct PortfolioMessageHandler {
|
||||
pub persistent_data: PersistentData,
|
||||
pub executor: NodeGraphExecutor,
|
||||
pub selection_mode: SelectionMode,
|
||||
/// The spreadsheet UI allows for instance data to be previewed.
|
||||
pub spreadsheet: SpreadsheetMessageHandler,
|
||||
device_pixel_ratio: Option<f64>,
|
||||
pub reset_node_definitions_on_open: bool,
|
||||
pub data_panel_open: bool,
|
||||
#[derivative(Default(value = "true"))]
|
||||
pub layers_panel_open: bool,
|
||||
#[derivative(Default(value = "true"))]
|
||||
pub properties_panel_open: bool,
|
||||
}
|
||||
|
||||
#[message_handler_data]
|
||||
@@ -62,11 +71,11 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
|
||||
let PortfolioMessageContext {
|
||||
ipp,
|
||||
preferences,
|
||||
animation,
|
||||
current_tool,
|
||||
message_logging_verbosity,
|
||||
reset_node_definitions_on_open,
|
||||
timing_information,
|
||||
animation,
|
||||
} = context;
|
||||
|
||||
match message {
|
||||
@@ -80,8 +89,10 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
|
||||
self.menu_bar_message_handler.has_selected_nodes = false;
|
||||
self.menu_bar_message_handler.has_selected_layers = false;
|
||||
self.menu_bar_message_handler.has_selection_history = (false, false);
|
||||
self.menu_bar_message_handler.single_path_node_compatible_layer_selected = false;
|
||||
self.menu_bar_message_handler.spreadsheet_view_open = self.spreadsheet.spreadsheet_view_open;
|
||||
self.menu_bar_message_handler.make_path_editable_is_allowed = false;
|
||||
self.menu_bar_message_handler.data_panel_open = self.data_panel_open;
|
||||
self.menu_bar_message_handler.layers_panel_open = self.layers_panel_open;
|
||||
self.menu_bar_message_handler.properties_panel_open = self.properties_panel_open;
|
||||
self.menu_bar_message_handler.message_logging_verbosity = message_logging_verbosity;
|
||||
self.menu_bar_message_handler.reset_node_definitions_on_open = reset_node_definitions_on_open;
|
||||
|
||||
@@ -98,37 +109,11 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
|
||||
let metadata = &document.network_interface.document_network_metadata().persistent_metadata;
|
||||
(!metadata.selection_undo_history.is_empty(), !metadata.selection_redo_history.is_empty())
|
||||
};
|
||||
self.menu_bar_message_handler.single_path_node_compatible_layer_selected = {
|
||||
let selected_nodes = document.network_interface.selected_nodes();
|
||||
let mut selected_layers = selected_nodes.selected_layers(document.metadata());
|
||||
let first_layer = selected_layers.next();
|
||||
let second_layer = selected_layers.next();
|
||||
let has_single_selection = first_layer.is_some() && second_layer.is_none();
|
||||
|
||||
let compatible_type = first_layer.and_then(|layer| {
|
||||
let graph_layer = graph_modification_utils::NodeGraphLayer::new(layer, &document.network_interface);
|
||||
graph_layer.horizontal_layer_flow().nth(1).and_then(|node_id| {
|
||||
let (output_type, _) = document.network_interface.output_type(&node_id, 0, &[]);
|
||||
Some(format!("type:{}", output_type.nested_type()))
|
||||
})
|
||||
});
|
||||
|
||||
let is_compatible = compatible_type.as_deref() == Some("type:Instances<VectorData>");
|
||||
|
||||
let is_modifiable = first_layer.map_or(false, |layer| {
|
||||
let graph_layer = graph_modification_utils::NodeGraphLayer::new(layer, &document.network_interface);
|
||||
matches!(graph_layer.find_input("Path", 1), Some(TaggedValue::VectorModification(_)))
|
||||
});
|
||||
|
||||
first_layer.is_some() && has_single_selection && is_compatible && !is_modifiable
|
||||
}
|
||||
self.menu_bar_message_handler.make_path_editable_is_allowed = make_path_editable_is_allowed(&mut document.network_interface).is_some();
|
||||
}
|
||||
|
||||
self.menu_bar_message_handler.process_message(message, responses, ());
|
||||
}
|
||||
PortfolioMessage::Spreadsheet(message) => {
|
||||
self.spreadsheet.process_message(message, responses, ());
|
||||
}
|
||||
PortfolioMessage::Document(message) => {
|
||||
if let Some(document_id) = self.active_document_id {
|
||||
if let Some(document) = self.documents.get_mut(&document_id) {
|
||||
@@ -140,6 +125,9 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
|
||||
current_tool,
|
||||
preferences,
|
||||
device_pixel_ratio: self.device_pixel_ratio.unwrap_or(1.),
|
||||
data_panel_open: self.data_panel_open,
|
||||
layers_panel_open: self.layers_panel_open,
|
||||
properties_panel_open: self.properties_panel_open,
|
||||
};
|
||||
document.process_message(message, responses, document_inputs)
|
||||
}
|
||||
@@ -174,6 +162,9 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
|
||||
current_tool,
|
||||
preferences,
|
||||
device_pixel_ratio: self.device_pixel_ratio.unwrap_or(1.),
|
||||
data_panel_open: self.data_panel_open,
|
||||
layers_panel_open: self.layers_panel_open,
|
||||
properties_panel_open: self.properties_panel_open,
|
||||
};
|
||||
document.process_message(message, responses, document_inputs)
|
||||
}
|
||||
@@ -213,12 +204,13 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
|
||||
}
|
||||
PortfolioMessage::CloseAllDocuments => {
|
||||
if self.active_document_id.is_some() {
|
||||
responses.add(BroadcastEvent::ToolAbort);
|
||||
responses.add(EventMessage::ToolAbort);
|
||||
responses.add(ToolMessage::DeactivateTools);
|
||||
|
||||
// Clear relevant UI layouts if there are no documents
|
||||
responses.add(PropertiesPanelMessage::Clear);
|
||||
responses.add(DocumentMessage::ClearLayersPanel);
|
||||
responses.add(DataPanelMessage::ClearLayout);
|
||||
let hint_data = HintData(vec![HintGroup(vec![])]);
|
||||
responses.add(FrontendMessage::UpdateInputHints { hint_data });
|
||||
}
|
||||
@@ -243,6 +235,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
|
||||
// Clear UI layouts that assume the existence of a document
|
||||
responses.add(PropertiesPanelMessage::Clear);
|
||||
responses.add(DocumentMessage::ClearLayersPanel);
|
||||
responses.add(DataPanelMessage::ClearLayout);
|
||||
let hint_data = HintData(vec![HintGroup(vec![])]);
|
||||
responses.add(FrontendMessage::UpdateInputHints { hint_data });
|
||||
}
|
||||
@@ -257,7 +250,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
|
||||
PortfolioMessage::CloseDocumentWithConfirmation { document_id } => {
|
||||
let target_document = self.documents.get(&document_id).unwrap();
|
||||
if target_document.is_saved() {
|
||||
responses.add(BroadcastEvent::ToolAbort);
|
||||
responses.add(EventMessage::ToolAbort);
|
||||
responses.add(PortfolioMessage::CloseDocument { document_id });
|
||||
} else {
|
||||
let dialog = simple_dialogs::CloseDocumentDialog {
|
||||
@@ -362,14 +355,17 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
|
||||
self.persistent_data.font_cache.insert(font, preview_url, data);
|
||||
self.executor.update_font_cache(self.persistent_data.font_cache.clone());
|
||||
for document_id in self.document_ids.iter() {
|
||||
let inspect_node = self.inspect_node_id();
|
||||
let _ = self.executor.submit_node_graph_evaluation(
|
||||
let node_to_inspect = self.node_to_inspect();
|
||||
if let Ok(message) = self.executor.submit_node_graph_evaluation(
|
||||
self.documents.get_mut(document_id).expect("Tried to render non-existent document"),
|
||||
*document_id,
|
||||
ipp.viewport_bounds.size().as_uvec2(),
|
||||
timing_information,
|
||||
inspect_node,
|
||||
node_to_inspect,
|
||||
true,
|
||||
);
|
||||
) {
|
||||
responses.add_front(message);
|
||||
}
|
||||
}
|
||||
|
||||
if self.active_document_mut().is_some() {
|
||||
@@ -394,16 +390,19 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
|
||||
PortfolioMessage::NewDocumentWithName { name } => {
|
||||
let mut new_document = DocumentMessageHandler::default();
|
||||
new_document.name = name;
|
||||
responses.add(DocumentMessage::PTZUpdate);
|
||||
let mut new_responses = VecDeque::new();
|
||||
new_responses.add(DocumentMessage::PTZUpdate);
|
||||
|
||||
let document_id = DocumentId(generate_uuid());
|
||||
if self.active_document().is_some() {
|
||||
responses.add(BroadcastEvent::ToolAbort);
|
||||
responses.add(NavigationMessage::CanvasPan { delta: (0., 0.).into() });
|
||||
new_responses.add(EventMessage::ToolAbort);
|
||||
new_responses.add(NavigationMessage::CanvasPan { delta: (0., 0.).into() });
|
||||
}
|
||||
|
||||
self.load_document(new_document, document_id, responses, false);
|
||||
responses.add(PortfolioMessage::SelectDocument { document_id });
|
||||
self.load_document(new_document, document_id, self.layers_panel_open, &mut new_responses, false);
|
||||
new_responses.add(PortfolioMessage::SelectDocument { document_id });
|
||||
new_responses.extend(responses.drain(..));
|
||||
*responses = new_responses;
|
||||
}
|
||||
PortfolioMessage::NextDocument => {
|
||||
if let Some(active_document_id) = self.active_document_id {
|
||||
@@ -420,12 +419,14 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
|
||||
}
|
||||
PortfolioMessage::OpenDocumentFile {
|
||||
document_name,
|
||||
document_path,
|
||||
document_serialized_content,
|
||||
} => {
|
||||
let document_id = DocumentId(generate_uuid());
|
||||
responses.add(PortfolioMessage::OpenDocumentFileWithId {
|
||||
document_id,
|
||||
document_name,
|
||||
document_path,
|
||||
document_is_auto_saved: false,
|
||||
document_is_saved: true,
|
||||
document_serialized_content,
|
||||
@@ -440,6 +441,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
|
||||
PortfolioMessage::OpenDocumentFileWithId {
|
||||
document_id,
|
||||
document_name,
|
||||
document_path,
|
||||
document_is_auto_saved,
|
||||
document_is_saved,
|
||||
document_serialized_content,
|
||||
@@ -451,10 +453,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
|
||||
let document_serialized_content = document_migration_string_preprocessing(document_serialized_content);
|
||||
|
||||
// Deserialize the document
|
||||
let document = DocumentMessageHandler::deserialize_document(&document_serialized_content).map(|mut document| {
|
||||
document.name.clone_from(&document_name);
|
||||
document
|
||||
});
|
||||
let document = DocumentMessageHandler::deserialize_document(&document_serialized_content);
|
||||
|
||||
// Display an error to the user if the document could not be opened
|
||||
let mut document = match document {
|
||||
@@ -515,8 +514,32 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
|
||||
document.set_auto_save_state(document_is_auto_saved);
|
||||
document.set_save_state(document_is_saved);
|
||||
|
||||
let document_name_from_path = document_path.as_ref().and_then(|path| {
|
||||
if path.extension().is_some_and(|e| e == FILE_EXTENSION) {
|
||||
path.file_stem().map(|n| n.to_string_lossy().to_string())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
|
||||
match (document_name, document_path, document_name_from_path) {
|
||||
(Some(name), _, None) => {
|
||||
document.name = name;
|
||||
}
|
||||
(_, Some(path), Some(name)) => {
|
||||
document.name = name;
|
||||
document.path = Some(path);
|
||||
}
|
||||
(_, _, Some(name)) => {
|
||||
document.name = name;
|
||||
}
|
||||
_ => {
|
||||
document.name = DEFAULT_DOCUMENT_NAME.to_string();
|
||||
}
|
||||
}
|
||||
|
||||
// Load the document into the portfolio so it opens in the editor
|
||||
self.load_document(document, document_id, responses, to_front);
|
||||
self.load_document(document, document_id, self.layers_panel_open, responses, to_front);
|
||||
}
|
||||
PortfolioMessage::PasteIntoFolder { clipboard, parent, insert_index } => {
|
||||
let mut all_new_ids = Vec::new();
|
||||
@@ -568,11 +591,105 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
|
||||
|
||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||
responses.add(NodeGraphMessage::SelectedNodesSet { nodes: all_new_ids });
|
||||
responses.add(Message::StartBuffer);
|
||||
responses.add(PortfolioMessage::CenterPastedLayers { layers });
|
||||
responses.add(DeferMessage::AfterGraphRun {
|
||||
messages: vec![PortfolioMessage::CenterPastedLayers { layers }.into()],
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
// Custom paste implementation for Path tool
|
||||
PortfolioMessage::PasteSerializedVector { data } => {
|
||||
// If using Path tool then send the operation to Path tool
|
||||
if *current_tool == ToolType::Path {
|
||||
responses.add(PathToolMessage::Paste { data });
|
||||
return;
|
||||
}
|
||||
|
||||
// If not using Path tool, create new layers and add paths into those
|
||||
if let Some(document) = self.active_document() {
|
||||
let Ok(data) = serde_json::from_str::<Vec<(LayerNodeIdentifier, Vector, DAffine2)>>(&data) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let mut layers = Vec::new();
|
||||
|
||||
for (_, new_vector, transform) in data {
|
||||
let Some(node_type) = resolve_document_node_type("Path") else {
|
||||
error!("Path node does not exist");
|
||||
continue;
|
||||
};
|
||||
let nodes = vec![(NodeId(0), node_type.default_node_template())];
|
||||
|
||||
let parent = document.new_layer_parent(false);
|
||||
|
||||
let layer = graph_modification_utils::new_custom(NodeId::new(), nodes, parent, responses);
|
||||
layers.push(layer);
|
||||
|
||||
// Adding the transform back into the layer
|
||||
responses.add(GraphOperationMessage::TransformSet {
|
||||
layer,
|
||||
transform,
|
||||
transform_in: TransformIn::Local,
|
||||
skip_rerender: false,
|
||||
});
|
||||
|
||||
// Add default fill and stroke to the layer
|
||||
let fill_color = Color::WHITE;
|
||||
let stroke_color = Color::BLACK;
|
||||
|
||||
let fill = graphene_std::vector::style::Fill::solid(fill_color.to_gamma_srgb());
|
||||
responses.add(GraphOperationMessage::FillSet { layer, fill });
|
||||
|
||||
let stroke = graphene_std::vector::style::Stroke::new(Some(stroke_color.to_gamma_srgb()), DEFAULT_STROKE_WIDTH);
|
||||
responses.add(GraphOperationMessage::StrokeSet { layer, stroke });
|
||||
|
||||
// Create new point ids and add those into the existing Vector path
|
||||
let mut points_map = HashMap::new();
|
||||
for (point, position) in new_vector.point_domain.iter() {
|
||||
let new_point_id = PointId::generate();
|
||||
points_map.insert(point, new_point_id);
|
||||
let modification_type = VectorModificationType::InsertPoint { id: new_point_id, position };
|
||||
responses.add(GraphOperationMessage::Vector { layer, modification_type });
|
||||
}
|
||||
|
||||
// Create new segment ids and add the segments into the existing Vector path
|
||||
let mut segments_map = HashMap::new();
|
||||
for (segment_id, bezier, start, end) in new_vector.segment_bezier_iter() {
|
||||
let new_segment_id = SegmentId::generate();
|
||||
|
||||
segments_map.insert(segment_id, new_segment_id);
|
||||
|
||||
let handles = match bezier.handles {
|
||||
BezierHandles::Linear => [None, None],
|
||||
BezierHandles::Quadratic { handle } => [Some(handle - bezier.start), None],
|
||||
BezierHandles::Cubic { handle_start, handle_end } => [Some(handle_start - bezier.start), Some(handle_end - bezier.end)],
|
||||
};
|
||||
|
||||
let points = [points_map[&start], points_map[&end]];
|
||||
let modification_type = VectorModificationType::InsertSegment { id: new_segment_id, points, handles };
|
||||
responses.add(GraphOperationMessage::Vector { layer, modification_type });
|
||||
}
|
||||
|
||||
// Set G1 continuity
|
||||
for handles in new_vector.colinear_manipulators {
|
||||
let to_new_handle = |handle: HandleId| -> HandleId {
|
||||
HandleId {
|
||||
ty: handle.ty,
|
||||
segment: segments_map[&handle.segment],
|
||||
}
|
||||
};
|
||||
let new_handles = [to_new_handle(handles[0]), to_new_handle(handles[1])];
|
||||
let modification_type = VectorModificationType::SetG1Continuous { handles: new_handles, enabled: true };
|
||||
responses.add(GraphOperationMessage::Vector { layer, modification_type });
|
||||
}
|
||||
}
|
||||
|
||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||
responses.add(Message::Defer(DeferMessage::AfterGraphRun {
|
||||
messages: vec![PortfolioMessage::CenterPastedLayers { layers }.into()],
|
||||
}));
|
||||
}
|
||||
}
|
||||
PortfolioMessage::CenterPastedLayers { layers } => {
|
||||
if let Some(document) = self.active_document_mut() {
|
||||
let viewport_bounds_quad_pixels = Quad::from_box([DVec2::ZERO, ipp.viewport_bounds.size()]);
|
||||
@@ -688,7 +805,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
|
||||
|
||||
if create_document {
|
||||
responses.add(PortfolioMessage::NewDocumentWithName {
|
||||
name: name.clone().unwrap_or("Untitled Document".into()),
|
||||
name: name.clone().unwrap_or(DEFAULT_DOCUMENT_NAME.into()),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -701,13 +818,12 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
|
||||
|
||||
if create_document {
|
||||
// Wait for the document to be rendered so the click targets can be calculated in order to determine the artboard size that will encompass the pasted image
|
||||
responses.add(Message::StartBuffer);
|
||||
responses.add(DocumentMessage::WrapContentInArtboard { place_artboard_at_origin: true });
|
||||
|
||||
// TODO: Figure out how to get StartBuffer to work here so we can delete this and use `DocumentMessage::ZoomCanvasToFitAll` instead
|
||||
// Currently, it is necessary to use `FrontendMessage::TriggerDelayedZoomCanvasToFitAll` rather than `DocumentMessage::ZoomCanvasToFitAll` because the size of the viewport is not yet populated
|
||||
responses.add(Message::StartBuffer);
|
||||
responses.add(FrontendMessage::TriggerDelayedZoomCanvasToFitAll);
|
||||
responses.add(DeferMessage::AfterGraphRun {
|
||||
messages: vec![DocumentMessage::WrapContentInArtboard { place_artboard_at_origin: true }.into()],
|
||||
});
|
||||
responses.add(DeferMessage::AfterNavigationReady {
|
||||
messages: vec![DocumentMessage::ZoomCanvasToFitAll.into()],
|
||||
});
|
||||
}
|
||||
}
|
||||
PortfolioMessage::PasteSvg {
|
||||
@@ -720,7 +836,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
|
||||
|
||||
if create_document {
|
||||
responses.add(PortfolioMessage::NewDocumentWithName {
|
||||
name: name.clone().unwrap_or("Untitled Document".into()),
|
||||
name: name.clone().unwrap_or(DEFAULT_DOCUMENT_NAME.into()),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -733,13 +849,13 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
|
||||
|
||||
if create_document {
|
||||
// Wait for the document to be rendered so the click targets can be calculated in order to determine the artboard size that will encompass the pasted image
|
||||
responses.add(Message::StartBuffer);
|
||||
responses.add(DocumentMessage::WrapContentInArtboard { place_artboard_at_origin: true });
|
||||
responses.add(DeferMessage::AfterGraphRun {
|
||||
messages: vec![DocumentMessage::WrapContentInArtboard { place_artboard_at_origin: true }.into()],
|
||||
});
|
||||
|
||||
// TODO: Figure out how to get StartBuffer to work here so we can delete this and use `DocumentMessage::ZoomCanvasToFitAll` instead
|
||||
// Currently, it is necessary to use `FrontendMessage::TriggerDelayedZoomCanvasToFitAll` rather than `DocumentMessage::ZoomCanvasToFitAll` because the size of the viewport is not yet populated
|
||||
responses.add(Message::StartBuffer);
|
||||
responses.add(FrontendMessage::TriggerDelayedZoomCanvasToFitAll);
|
||||
responses.add(DeferMessage::AfterNavigationReady {
|
||||
messages: vec![DocumentMessage::ZoomCanvasToFitAll.into()],
|
||||
});
|
||||
}
|
||||
}
|
||||
PortfolioMessage::PrevDocument => {
|
||||
@@ -782,8 +898,8 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
|
||||
responses.add(ToolMessage::InitTools);
|
||||
responses.add(NodeGraphMessage::Init);
|
||||
responses.add(OverlaysMessage::Draw);
|
||||
responses.add(BroadcastEvent::ToolAbort);
|
||||
responses.add(BroadcastEvent::SelectionChanged);
|
||||
responses.add(EventMessage::ToolAbort);
|
||||
responses.add(EventMessage::SelectionChanged);
|
||||
responses.add(NavigationMessage::CanvasPan { delta: (0., 0.).into() });
|
||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||
responses.add(DocumentMessage::GraphViewOverlay { open: node_graph_open });
|
||||
@@ -807,7 +923,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
|
||||
}
|
||||
}
|
||||
PortfolioMessage::SubmitDocumentExport {
|
||||
file_name,
|
||||
name,
|
||||
file_type,
|
||||
scale_factor,
|
||||
bounds,
|
||||
@@ -815,14 +931,14 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
|
||||
} => {
|
||||
let document = self.active_document_id.and_then(|id| self.documents.get_mut(&id)).expect("Tried to render non-existent document");
|
||||
let export_config = ExportConfig {
|
||||
file_name,
|
||||
name,
|
||||
file_type,
|
||||
scale_factor,
|
||||
bounds,
|
||||
transparent_background,
|
||||
..Default::default()
|
||||
};
|
||||
let result = self.executor.submit_document_export(document, export_config);
|
||||
let result = self.executor.submit_document_export(document, self.active_document_id.unwrap(), export_config);
|
||||
|
||||
if let Err(description) = result {
|
||||
responses.add(DialogMessage::DisplayDialogError {
|
||||
@@ -837,20 +953,76 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
|
||||
}
|
||||
}
|
||||
PortfolioMessage::SubmitGraphRender { document_id, ignore_hash } => {
|
||||
let inspect_node = self.inspect_node_id();
|
||||
let node_to_inspect = self.node_to_inspect();
|
||||
let result = self.executor.submit_node_graph_evaluation(
|
||||
self.documents.get_mut(&document_id).expect("Tried to render non-existent document"),
|
||||
document_id,
|
||||
ipp.viewport_bounds.size().as_uvec2(),
|
||||
timing_information,
|
||||
inspect_node,
|
||||
node_to_inspect,
|
||||
ignore_hash,
|
||||
);
|
||||
|
||||
if let Err(description) = result {
|
||||
responses.add(DialogMessage::DisplayDialogError {
|
||||
title: "Unable to update node graph".to_string(),
|
||||
description,
|
||||
match result {
|
||||
Err(description) => {
|
||||
responses.add(DialogMessage::DisplayDialogError {
|
||||
title: "Unable to update node graph".to_string(),
|
||||
description,
|
||||
});
|
||||
}
|
||||
Ok(message) => responses.add_front(message),
|
||||
}
|
||||
}
|
||||
PortfolioMessage::ToggleDataPanelOpen => {
|
||||
self.data_panel_open = !self.data_panel_open;
|
||||
responses.add(MenuBarMessage::SendLayout);
|
||||
|
||||
// Run the graph to grab the data
|
||||
if self.data_panel_open {
|
||||
// When opening, we make the frontend show the panel first so it can start receiving its message subscriptions for the data it will display
|
||||
responses.add(FrontendMessage::UpdateDataPanelState { open: self.data_panel_open });
|
||||
|
||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||
} else {
|
||||
// If we don't clear the panel, the layout diffing system will assume widgets still exist when it attempts to update the data panel next time it is opened
|
||||
responses.add(DataPanelMessage::ClearLayout);
|
||||
|
||||
// When closing, we make the frontend hide the panel last so it can finish receiving its message subscriptions before it is destroyed
|
||||
responses.add(FrontendMessage::UpdateDataPanelState { open: self.data_panel_open });
|
||||
}
|
||||
}
|
||||
PortfolioMessage::TogglePropertiesPanelOpen => {
|
||||
self.properties_panel_open = !self.properties_panel_open;
|
||||
responses.add(MenuBarMessage::SendLayout);
|
||||
|
||||
responses.add(FrontendMessage::UpdatePropertiesPanelState { open: self.properties_panel_open });
|
||||
|
||||
// Run the graph to grab the data
|
||||
if self.properties_panel_open {
|
||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||
}
|
||||
|
||||
responses.add(PropertiesPanelMessage::Refresh);
|
||||
}
|
||||
PortfolioMessage::ToggleLayersPanelOpen => {
|
||||
self.layers_panel_open = !self.layers_panel_open;
|
||||
responses.add(MenuBarMessage::SendLayout);
|
||||
|
||||
// Run the graph to grab the data
|
||||
if self.layers_panel_open {
|
||||
// When opening, we make the frontend show the panel first so it can start receiving its message subscriptions for the data it will display
|
||||
responses.add(FrontendMessage::UpdateLayersPanelState { open: self.layers_panel_open });
|
||||
|
||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||
responses.add(DeferMessage::AfterGraphRun {
|
||||
messages: vec![NodeGraphMessage::UpdateLayerPanel.into(), DocumentMessage::DocumentStructureChanged.into()],
|
||||
});
|
||||
} else {
|
||||
// If we don't clear the panel, the layout diffing system will assume widgets still exist when it attempts to update the layers panel next time it is opened
|
||||
responses.add(DocumentMessage::ClearLayersPanel);
|
||||
|
||||
// When closing, we make the frontend hide the panel last so it can finish receiving its message subscriptions before it is destroyed
|
||||
responses.add(FrontendMessage::UpdateLayersPanelState { open: self.layers_panel_open });
|
||||
}
|
||||
}
|
||||
PortfolioMessage::ToggleRulers => {
|
||||
@@ -883,6 +1055,8 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
|
||||
responses.add(FrontendMessage::UpdateOpenDocumentsList { open_documents });
|
||||
}
|
||||
PortfolioMessage::UpdateVelloPreference => {
|
||||
let active = if cfg!(target_family = "wasm") { false } else { preferences.use_vello };
|
||||
responses.add(FrontendMessage::UpdateViewportHolePunch { active });
|
||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||
self.persistent_data.use_vello = preferences.use_vello;
|
||||
}
|
||||
@@ -900,6 +1074,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
|
||||
PasteIntoFolder,
|
||||
PrevDocument,
|
||||
ToggleRulers,
|
||||
ToggleDataPanelOpen,
|
||||
);
|
||||
|
||||
// Extend with actions that require an active document
|
||||
@@ -969,19 +1144,19 @@ impl PortfolioMessageHandler {
|
||||
}
|
||||
}
|
||||
|
||||
fn load_document(&mut self, new_document: DocumentMessageHandler, document_id: DocumentId, responses: &mut VecDeque<Message>, to_front: bool) {
|
||||
fn load_document(&mut self, mut new_document: DocumentMessageHandler, document_id: DocumentId, layers_panel_open: bool, responses: &mut VecDeque<Message>, to_front: bool) {
|
||||
if to_front {
|
||||
self.document_ids.push_front(document_id);
|
||||
} else {
|
||||
self.document_ids.push_back(document_id);
|
||||
}
|
||||
new_document.update_layers_panel_control_bar_widgets(responses);
|
||||
new_document.update_layers_panel_bottom_bar_widgets(responses);
|
||||
new_document.update_layers_panel_control_bar_widgets(layers_panel_open, responses);
|
||||
new_document.update_layers_panel_bottom_bar_widgets(layers_panel_open, responses);
|
||||
|
||||
self.documents.insert(document_id, new_document);
|
||||
|
||||
if self.active_document().is_some() {
|
||||
responses.add(BroadcastEvent::ToolAbort);
|
||||
responses.add(EventMessage::ToolAbort);
|
||||
responses.add(ToolMessage::DeactivateTools);
|
||||
} else {
|
||||
// Load the default font upon creating the first document
|
||||
@@ -1019,25 +1194,22 @@ impl PortfolioMessageHandler {
|
||||
/text>"#
|
||||
// It's a mystery why the `/text>` tag above needs to be missing its `<`, but when it exists it prints the `<` character in the text. However this works with it removed.
|
||||
.to_string();
|
||||
responses.add(Message::EndBuffer {
|
||||
render_metadata: graphene_std::renderer::RenderMetadata::default(),
|
||||
});
|
||||
responses.add(FrontendMessage::UpdateDocumentArtwork { svg: error });
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
/// Get the id of the node that should be used as the target for the spreadsheet
|
||||
pub fn inspect_node_id(&self) -> Option<NodeId> {
|
||||
// Spreadsheet not open, skipping
|
||||
if !self.spreadsheet.spreadsheet_view_open {
|
||||
/// Get the ID of the selected node that should be used as the current source for the Data panel.
|
||||
pub fn node_to_inspect(&self) -> Option<NodeId> {
|
||||
// Skip if the Data panel is not open
|
||||
if !self.data_panel_open {
|
||||
return None;
|
||||
}
|
||||
|
||||
let document = self.documents.get(&self.active_document_id?)?;
|
||||
let selected_nodes = document.network_interface.selected_nodes().0;
|
||||
|
||||
// Selected nodes != 1, skipping
|
||||
// Skip if there is not exactly one selected node
|
||||
if selected_nodes.len() != 1 {
|
||||
return None;
|
||||
}
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
mod spreadsheet_message;
|
||||
mod spreadsheet_message_handler;
|
||||
|
||||
#[doc(inline)]
|
||||
pub use spreadsheet_message::*;
|
||||
#[doc(inline)]
|
||||
pub use spreadsheet_message_handler::*;
|
||||
@@ -43,7 +43,7 @@ pub enum PanelType {
|
||||
Document,
|
||||
Layers,
|
||||
Properties,
|
||||
Spreadsheet,
|
||||
DataPanel,
|
||||
}
|
||||
|
||||
impl From<String> for PanelType {
|
||||
@@ -52,8 +52,8 @@ impl From<String> for PanelType {
|
||||
"Document" => PanelType::Document,
|
||||
"Layers" => PanelType::Layers,
|
||||
"Properties" => PanelType::Properties,
|
||||
"Spreadsheet" => PanelType::Spreadsheet,
|
||||
_ => panic!("Unknown panel type: {}", value),
|
||||
"Data" => PanelType::DataPanel,
|
||||
_ => panic!("Unknown panel type: {value}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,7 +62,7 @@ impl MessageHandler<PreferencesMessage, ()> for PreferencesMessageHandler {
|
||||
}
|
||||
PreferencesMessage::ResetToDefaults => {
|
||||
refresh_dialog(responses);
|
||||
responses.add(KeyMappingMessage::ModifyMapping(MappingVariant::Default));
|
||||
responses.add(KeyMappingMessage::ModifyMapping { mapping: MappingVariant::Default });
|
||||
|
||||
*self = Self::default()
|
||||
}
|
||||
@@ -80,7 +80,7 @@ impl MessageHandler<PreferencesMessage, ()> for PreferencesMessageHandler {
|
||||
self.zoom_with_scroll = zoom_with_scroll;
|
||||
|
||||
let variant = if zoom_with_scroll { MappingVariant::ZoomWithScroll } else { MappingVariant::Default };
|
||||
responses.add(KeyMappingMessage::ModifyMapping(variant));
|
||||
responses.add(KeyMappingMessage::ModifyMapping { mapping: variant });
|
||||
}
|
||||
PreferencesMessage::SelectionMode { selection_mode } => {
|
||||
self.selection_mode = selection_mode;
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
// Root
|
||||
pub use crate::utility_traits::{ActionList, AsMessage, HierarchicalTree, MessageHandler, ToDiscriminant, TransitiveChild};
|
||||
// Message-related
|
||||
pub use crate::utility_traits::{ActionList, AsMessage, ExtractField, HierarchicalTree, MessageHandler, ToDiscriminant, TransitiveChild};
|
||||
pub use crate::utility_types::{DebugMessageTree, MessageData};
|
||||
|
||||
// Message, MessageData, MessageDiscriminant, MessageHandler
|
||||
pub use crate::messages::animation::{AnimationMessage, AnimationMessageDiscriminant, AnimationMessageHandler};
|
||||
pub use crate::messages::app_window::{AppWindowMessage, AppWindowMessageDiscriminant, AppWindowMessageHandler};
|
||||
pub use crate::messages::broadcast::event::{EventMessage, EventMessageContext, EventMessageDiscriminant, EventMessageHandler};
|
||||
pub use crate::messages::broadcast::{BroadcastMessage, BroadcastMessageDiscriminant, BroadcastMessageHandler};
|
||||
pub use crate::messages::debug::{DebugMessage, DebugMessageDiscriminant, DebugMessageHandler};
|
||||
pub use crate::messages::defer::{DeferMessage, DeferMessageDiscriminant, DeferMessageHandler};
|
||||
pub use crate::messages::dialog::export_dialog::{ExportDialogMessage, ExportDialogMessageContext, ExportDialogMessageDiscriminant, ExportDialogMessageHandler};
|
||||
pub use crate::messages::dialog::new_document_dialog::{NewDocumentDialogMessage, NewDocumentDialogMessageDiscriminant, NewDocumentDialogMessageHandler};
|
||||
pub use crate::messages::dialog::preferences_dialog::{PreferencesDialogMessage, PreferencesDialogMessageContext, PreferencesDialogMessageDiscriminant, PreferencesDialogMessageHandler};
|
||||
@@ -15,6 +19,7 @@ pub use crate::messages::input_mapper::key_mapping::{KeyMappingMessage, KeyMappi
|
||||
pub use crate::messages::input_mapper::{InputMapperMessage, InputMapperMessageContext, InputMapperMessageDiscriminant, InputMapperMessageHandler};
|
||||
pub use crate::messages::input_preprocessor::{InputPreprocessorMessage, InputPreprocessorMessageContext, InputPreprocessorMessageDiscriminant, InputPreprocessorMessageHandler};
|
||||
pub use crate::messages::layout::{LayoutMessage, LayoutMessageDiscriminant, LayoutMessageHandler};
|
||||
pub use crate::messages::portfolio::document::data_panel::{DataPanelMessage, DataPanelMessageDiscriminant};
|
||||
pub use crate::messages::portfolio::document::graph_operation::{GraphOperationMessage, GraphOperationMessageContext, GraphOperationMessageDiscriminant, GraphOperationMessageHandler};
|
||||
pub use crate::messages::portfolio::document::navigation::{NavigationMessage, NavigationMessageContext, NavigationMessageDiscriminant, NavigationMessageHandler};
|
||||
pub use crate::messages::portfolio::document::node_graph::{NodeGraphMessage, NodeGraphMessageDiscriminant, NodeGraphMessageHandler};
|
||||
@@ -22,15 +27,12 @@ pub use crate::messages::portfolio::document::overlays::{OverlaysMessage, Overla
|
||||
pub use crate::messages::portfolio::document::properties_panel::{PropertiesPanelMessage, PropertiesPanelMessageDiscriminant, PropertiesPanelMessageHandler};
|
||||
pub use crate::messages::portfolio::document::{DocumentMessage, DocumentMessageContext, DocumentMessageDiscriminant, DocumentMessageHandler};
|
||||
pub use crate::messages::portfolio::menu_bar::{MenuBarMessage, MenuBarMessageDiscriminant, MenuBarMessageHandler};
|
||||
pub use crate::messages::portfolio::spreadsheet::{SpreadsheetMessage, SpreadsheetMessageDiscriminant};
|
||||
pub use crate::messages::portfolio::{PortfolioMessage, PortfolioMessageContext, 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, ToolMessageContext, ToolMessageDiscriminant, ToolMessageHandler};
|
||||
pub use crate::messages::workspace::{WorkspaceMessage, WorkspaceMessageDiscriminant, WorkspaceMessageHandler};
|
||||
|
||||
// Message, MessageDiscriminant
|
||||
pub use crate::messages::broadcast::broadcast_event::{BroadcastEvent, BroadcastEventDiscriminant};
|
||||
pub use crate::messages::message::{Message, MessageDiscriminant};
|
||||
pub use crate::messages::tool::tool_messages::artboard_tool::{ArtboardToolMessage, ArtboardToolMessageDiscriminant};
|
||||
pub use crate::messages::tool::tool_messages::brush_tool::{BrushToolMessage, BrushToolMessageDiscriminant};
|
||||
@@ -46,7 +48,7 @@ pub use crate::messages::tool::tool_messages::shape_tool::{ShapeToolMessage, Sha
|
||||
pub use crate::messages::tool::tool_messages::spline_tool::{SplineToolMessage, SplineToolMessageDiscriminant};
|
||||
pub use crate::messages::tool::tool_messages::text_tool::{TextToolMessage, TextToolMessageDiscriminant};
|
||||
|
||||
// Helper
|
||||
// Helper/miscellaneous
|
||||
pub use crate::messages::globals::global_variables::*;
|
||||
pub use crate::messages::portfolio::document::utility_types::misc::DocumentId;
|
||||
pub use graphite_proc_macros::*;
|
||||
@@ -65,10 +67,12 @@ pub trait Responses {
|
||||
}
|
||||
|
||||
impl Responses for VecDeque<Message> {
|
||||
#[inline(always)]
|
||||
fn add(&mut self, message: impl Into<Message>) {
|
||||
self.push_back(message.into());
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn add_front(&mut self, message: impl Into<Message>) {
|
||||
self.push_front(message.into());
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ impl AutoPanning {
|
||||
|
||||
for message in messages {
|
||||
responses.add(BroadcastMessage::SubscribeEvent {
|
||||
on: BroadcastEvent::AnimationFrame,
|
||||
on: EventMessage::AnimationFrame,
|
||||
send: Box::new(message.clone()),
|
||||
});
|
||||
}
|
||||
@@ -27,8 +27,8 @@ impl AutoPanning {
|
||||
|
||||
for message in messages {
|
||||
responses.add(BroadcastMessage::UnsubscribeEvent {
|
||||
on: BroadcastEvent::AnimationFrame,
|
||||
message: Box::new(message.clone()),
|
||||
on: EventMessage::AnimationFrame,
|
||||
send: Box::new(message.clone()),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
use crate::messages::frontend::utility_types::MouseCursorIcon;
|
||||
use crate::messages::message::Message;
|
||||
use crate::messages::portfolio::document::overlays::utility_types::OverlayContext;
|
||||
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
||||
use crate::messages::prelude::{DocumentMessageHandler, InputPreprocessorMessageHandler};
|
||||
use crate::messages::tool::common_functionality::graph_modification_utils;
|
||||
use crate::messages::tool::common_functionality::shape_editor::ShapeState;
|
||||
use crate::messages::tool::common_functionality::shapes::arc_shape::ArcGizmoHandler;
|
||||
use crate::messages::tool::common_functionality::shapes::circle_shape::CircleGizmoHandler;
|
||||
use crate::messages::tool::common_functionality::shapes::polygon_shape::PolygonGizmoHandler;
|
||||
use crate::messages::tool::common_functionality::shapes::shape_utility::ShapeGizmoHandler;
|
||||
use crate::messages::tool::common_functionality::shapes::star_shape::StarGizmoHandler;
|
||||
@@ -23,6 +26,8 @@ pub enum ShapeGizmoHandlers {
|
||||
None,
|
||||
Star(StarGizmoHandler),
|
||||
Polygon(PolygonGizmoHandler),
|
||||
Arc(ArcGizmoHandler),
|
||||
Circle(CircleGizmoHandler),
|
||||
}
|
||||
|
||||
impl ShapeGizmoHandlers {
|
||||
@@ -32,6 +37,8 @@ impl ShapeGizmoHandlers {
|
||||
match self {
|
||||
Self::Star(_) => "star",
|
||||
Self::Polygon(_) => "polygon",
|
||||
Self::Arc(_) => "arc",
|
||||
Self::Circle(_) => "circle",
|
||||
Self::None => "none",
|
||||
}
|
||||
}
|
||||
@@ -41,6 +48,8 @@ impl ShapeGizmoHandlers {
|
||||
match self {
|
||||
Self::Star(h) => h.handle_state(layer, mouse_position, document, responses),
|
||||
Self::Polygon(h) => h.handle_state(layer, mouse_position, document, responses),
|
||||
Self::Arc(h) => h.handle_state(layer, mouse_position, document, responses),
|
||||
Self::Circle(h) => h.handle_state(layer, mouse_position, document, responses),
|
||||
Self::None => {}
|
||||
}
|
||||
}
|
||||
@@ -50,6 +59,8 @@ impl ShapeGizmoHandlers {
|
||||
match self {
|
||||
Self::Star(h) => h.is_any_gizmo_hovered(),
|
||||
Self::Polygon(h) => h.is_any_gizmo_hovered(),
|
||||
Self::Arc(h) => h.is_any_gizmo_hovered(),
|
||||
Self::Circle(h) => h.is_any_gizmo_hovered(),
|
||||
Self::None => false,
|
||||
}
|
||||
}
|
||||
@@ -59,6 +70,8 @@ impl ShapeGizmoHandlers {
|
||||
match self {
|
||||
Self::Star(h) => h.handle_click(),
|
||||
Self::Polygon(h) => h.handle_click(),
|
||||
Self::Arc(h) => h.handle_click(),
|
||||
Self::Circle(h) => h.handle_click(),
|
||||
Self::None => {}
|
||||
}
|
||||
}
|
||||
@@ -68,6 +81,8 @@ impl ShapeGizmoHandlers {
|
||||
match self {
|
||||
Self::Star(h) => h.handle_update(drag_start, document, input, responses),
|
||||
Self::Polygon(h) => h.handle_update(drag_start, document, input, responses),
|
||||
Self::Arc(h) => h.handle_update(drag_start, document, input, responses),
|
||||
Self::Circle(h) => h.handle_update(drag_start, document, input, responses),
|
||||
Self::None => {}
|
||||
}
|
||||
}
|
||||
@@ -77,6 +92,8 @@ impl ShapeGizmoHandlers {
|
||||
match self {
|
||||
Self::Star(h) => h.cleanup(),
|
||||
Self::Polygon(h) => h.cleanup(),
|
||||
Self::Arc(h) => h.cleanup(),
|
||||
Self::Circle(h) => h.cleanup(),
|
||||
Self::None => {}
|
||||
}
|
||||
}
|
||||
@@ -94,6 +111,8 @@ impl ShapeGizmoHandlers {
|
||||
match self {
|
||||
Self::Star(h) => h.overlays(document, layer, input, shape_editor, mouse_position, overlay_context),
|
||||
Self::Polygon(h) => h.overlays(document, layer, input, shape_editor, mouse_position, overlay_context),
|
||||
Self::Arc(h) => h.overlays(document, layer, input, shape_editor, mouse_position, overlay_context),
|
||||
Self::Circle(h) => h.overlays(document, layer, input, shape_editor, mouse_position, overlay_context),
|
||||
Self::None => {}
|
||||
}
|
||||
}
|
||||
@@ -110,9 +129,21 @@ impl ShapeGizmoHandlers {
|
||||
match self {
|
||||
Self::Star(h) => h.dragging_overlays(document, input, shape_editor, mouse_position, overlay_context),
|
||||
Self::Polygon(h) => h.dragging_overlays(document, input, shape_editor, mouse_position, overlay_context),
|
||||
Self::Arc(h) => h.dragging_overlays(document, input, shape_editor, mouse_position, overlay_context),
|
||||
Self::Circle(h) => h.dragging_overlays(document, input, shape_editor, mouse_position, overlay_context),
|
||||
Self::None => {}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn gizmo_cursor_icon(&self) -> Option<MouseCursorIcon> {
|
||||
match self {
|
||||
Self::Star(h) => h.mouse_cursor_icon(),
|
||||
Self::Polygon(h) => h.mouse_cursor_icon(),
|
||||
Self::Arc(h) => h.mouse_cursor_icon(),
|
||||
Self::Circle(h) => h.mouse_cursor_icon(),
|
||||
Self::None => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Central manager that coordinates shape gizmo handlers for interactive editing on the canvas.
|
||||
@@ -141,11 +172,18 @@ impl GizmoManager {
|
||||
if graph_modification_utils::get_star_id(layer, &document.network_interface).is_some() {
|
||||
return Some(ShapeGizmoHandlers::Star(StarGizmoHandler::default()));
|
||||
}
|
||||
|
||||
// Polygon
|
||||
if graph_modification_utils::get_polygon_id(layer, &document.network_interface).is_some() {
|
||||
return Some(ShapeGizmoHandlers::Polygon(PolygonGizmoHandler::default()));
|
||||
}
|
||||
// Arc
|
||||
if graph_modification_utils::get_arc_id(layer, &document.network_interface).is_some() {
|
||||
return Some(ShapeGizmoHandlers::Arc(ArcGizmoHandler::new()));
|
||||
}
|
||||
// Circle
|
||||
if graph_modification_utils::get_circle_id(layer, &document.network_interface).is_some() {
|
||||
return Some(ShapeGizmoHandlers::Circle(CircleGizmoHandler::default()));
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
@@ -243,4 +281,12 @@ impl GizmoManager {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the cursor icon to display when hovering or dragging a gizmo.
|
||||
///
|
||||
/// If a gizmo is active (hovered or being manipulated), it returns the cursor icon associated with that gizmo;
|
||||
/// otherwise, returns `None` to indicate the default crosshair cursor should be used.
|
||||
pub fn mouse_cursor_icon(&self) -> Option<MouseCursorIcon> {
|
||||
self.active_shape_handler.as_ref().and_then(|h| h.gizmo_cursor_icon())
|
||||
}
|
||||
}
|
||||
|
||||
+177
@@ -0,0 +1,177 @@
|
||||
use crate::consts::GIZMO_HIDE_THRESHOLD;
|
||||
use crate::messages::frontend::utility_types::MouseCursorIcon;
|
||||
use crate::messages::message::Message;
|
||||
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::network_interface::InputConnector;
|
||||
use crate::messages::prelude::{DocumentMessageHandler, InputPreprocessorMessageHandler, NodeGraphMessage};
|
||||
use crate::messages::prelude::{FrontendMessage, Responses};
|
||||
use crate::messages::tool::common_functionality::graph_modification_utils::{self, get_arc_id, get_stroke_width};
|
||||
use crate::messages::tool::common_functionality::shapes::shape_utility::{extract_arc_parameters, extract_circle_radius};
|
||||
use glam::{DAffine2, DVec2};
|
||||
use graph_craft::document::NodeInput;
|
||||
use graph_craft::document::value::TaggedValue;
|
||||
use std::collections::VecDeque;
|
||||
use std::f64::consts::FRAC_PI_2;
|
||||
|
||||
#[derive(Clone, Debug, Default, PartialEq)]
|
||||
pub enum RadiusHandleState {
|
||||
#[default]
|
||||
Inactive,
|
||||
Hover,
|
||||
Dragging,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, PartialEq)]
|
||||
pub struct RadiusHandle {
|
||||
pub layer: Option<LayerNodeIdentifier>,
|
||||
initial_radius: f64,
|
||||
handle_state: RadiusHandleState,
|
||||
angle: f64,
|
||||
previous_mouse_position: DVec2,
|
||||
}
|
||||
|
||||
impl RadiusHandle {
|
||||
pub fn cleanup(&mut self) {
|
||||
self.handle_state = RadiusHandleState::Inactive;
|
||||
self.layer = None;
|
||||
}
|
||||
|
||||
pub fn hovered(&self) -> bool {
|
||||
self.handle_state == RadiusHandleState::Hover
|
||||
}
|
||||
|
||||
pub fn is_dragging(&self) -> bool {
|
||||
self.handle_state == RadiusHandleState::Dragging
|
||||
}
|
||||
|
||||
pub fn update_state(&mut self, state: RadiusHandleState) {
|
||||
self.handle_state = state;
|
||||
}
|
||||
|
||||
pub fn check_if_inside_dash_lines(angle: f64, mouse_position: DVec2, viewport: DAffine2, radius: f64, document: &DocumentMessageHandler, layer: LayerNodeIdentifier) -> bool {
|
||||
let center = viewport.transform_point2(DVec2::ZERO);
|
||||
if let Some(stroke_width) = get_stroke_width(layer, &document.network_interface) {
|
||||
let circle_point = calculate_circle_point_position(angle, radius.abs());
|
||||
let direction = circle_point.normalize();
|
||||
let mouse_distance = mouse_position.distance(center);
|
||||
|
||||
let spacing = Self::calculate_extra_spacing(viewport, radius, center, stroke_width, 15.);
|
||||
|
||||
let inner_point = viewport.transform_point2(circle_point - direction * spacing).distance(center);
|
||||
let outer_point = viewport.transform_point2(circle_point + direction * spacing).distance(center);
|
||||
|
||||
mouse_distance >= inner_point && mouse_distance <= outer_point
|
||||
} else {
|
||||
let point_position = viewport.transform_point2(calculate_circle_point_position(angle, radius.abs()));
|
||||
mouse_position.distance(center) <= point_position.distance(center)
|
||||
}
|
||||
}
|
||||
|
||||
fn calculate_extra_spacing(viewport: DAffine2, radius: f64, viewport_center: DVec2, stroke_width: f64, threshold: f64) -> f64 {
|
||||
let start_point = viewport.transform_point2(calculate_circle_point_position(0., radius)).distance(viewport_center);
|
||||
let end_point = viewport.transform_point2(calculate_circle_point_position(FRAC_PI_2, radius)).distance(viewport_center);
|
||||
let min_radius = start_point.min(end_point);
|
||||
let extra_spacing = if min_radius < threshold { 10. * (min_radius / threshold) } else { 10. };
|
||||
|
||||
stroke_width + extra_spacing
|
||||
}
|
||||
|
||||
pub fn handle_actions(&mut self, layer: LayerNodeIdentifier, document: &DocumentMessageHandler, mouse_position: DVec2, responses: &mut VecDeque<Message>) {
|
||||
match &self.handle_state {
|
||||
RadiusHandleState::Inactive => {
|
||||
let Some(radius) = extract_circle_radius(layer, document).or(extract_arc_parameters(Some(layer), document).map(|(r, _, _, _)| r)) else {
|
||||
return;
|
||||
};
|
||||
let viewport = document.metadata().transform_to_viewport(layer);
|
||||
let angle = viewport.inverse().transform_point2(mouse_position).angle_to(DVec2::X);
|
||||
let point_position = viewport.transform_point2(calculate_circle_point_position(angle, radius.abs()));
|
||||
let center = viewport.transform_point2(DVec2::ZERO);
|
||||
|
||||
if point_position.distance(center) < GIZMO_HIDE_THRESHOLD {
|
||||
return;
|
||||
}
|
||||
|
||||
if Self::check_if_inside_dash_lines(angle, mouse_position, viewport, radius.abs(), document, layer) {
|
||||
self.layer = Some(layer);
|
||||
self.initial_radius = radius;
|
||||
self.previous_mouse_position = mouse_position;
|
||||
self.angle = angle;
|
||||
|
||||
self.update_state(RadiusHandleState::Hover);
|
||||
|
||||
responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::EWResize });
|
||||
}
|
||||
}
|
||||
RadiusHandleState::Dragging | RadiusHandleState::Hover => {}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn overlays(&self, document: &DocumentMessageHandler, overlay_context: &mut OverlayContext) {
|
||||
match &self.handle_state {
|
||||
RadiusHandleState::Inactive => {}
|
||||
RadiusHandleState::Dragging | RadiusHandleState::Hover => {
|
||||
let Some(layer) = self.layer else { return };
|
||||
let Some(radius) = extract_circle_radius(layer, document).or(extract_arc_parameters(Some(layer), document).map(|(r, _, _, _)| r)) else {
|
||||
return;
|
||||
};
|
||||
let viewport = document.metadata().transform_to_viewport(layer);
|
||||
let center = viewport.transform_point2(DVec2::ZERO);
|
||||
|
||||
let x_point = viewport.transform_point2(calculate_circle_point_position(0., radius));
|
||||
let y_point = viewport.transform_point2(calculate_circle_point_position(FRAC_PI_2, radius));
|
||||
|
||||
let direction_x = viewport.transform_vector2(DVec2::X);
|
||||
let direction_y = viewport.transform_vector2(-DVec2::Y);
|
||||
|
||||
if let Some(stroke_width) = get_stroke_width(layer, &document.network_interface) {
|
||||
let spacing = Self::calculate_extra_spacing(viewport, radius, center, stroke_width, 15.);
|
||||
let smaller_radius_x = (x_point - direction_x * spacing).distance(center);
|
||||
let smaller_radius_y = (y_point - direction_y * spacing).distance(center);
|
||||
|
||||
let larger_radius_x = (x_point + direction_x * spacing).distance(center);
|
||||
let larger_radius_y = (y_point + direction_y * spacing).distance(center);
|
||||
|
||||
overlay_context.dashed_ellipse(center, smaller_radius_x, smaller_radius_y, None, None, None, None, None, None, Some(4.), Some(4.), Some(0.5));
|
||||
overlay_context.dashed_ellipse(center, larger_radius_x, larger_radius_y, None, None, None, None, None, None, Some(4.), Some(4.), Some(0.5));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
let radius_x = x_point.distance(center);
|
||||
let radius_y = y_point.distance(center);
|
||||
overlay_context.dashed_ellipse(center, radius_x, radius_y, None, None, None, None, None, None, Some(4.), Some(4.), Some(0.5));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_inner_radius(&mut self, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque<Message>, drag_start: DVec2) {
|
||||
let Some(layer) = self.layer else { return };
|
||||
let Some(node_id) = graph_modification_utils::get_circle_id(layer, &document.network_interface).or(get_arc_id(layer, &document.network_interface)) else {
|
||||
return;
|
||||
};
|
||||
let Some(current_radius) = extract_circle_radius(layer, document).or(extract_arc_parameters(Some(layer), document).map(|(r, _, _, _)| r)) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let viewport_transform = document.network_interface.document_metadata().transform_to_viewport(layer);
|
||||
let center = viewport_transform.transform_point2(DVec2::ZERO);
|
||||
|
||||
let delta_vector = viewport_transform.inverse().transform_point2(input.mouse.position) - viewport_transform.inverse().transform_point2(self.previous_mouse_position);
|
||||
let radius = drag_start - center;
|
||||
let sign = radius.dot(delta_vector).signum();
|
||||
|
||||
let net_delta = delta_vector.length() * sign * self.initial_radius.signum();
|
||||
self.previous_mouse_position = input.mouse.position;
|
||||
|
||||
responses.add(NodeGraphMessage::SetInput {
|
||||
input_connector: InputConnector::node(node_id, 1),
|
||||
input: NodeInput::value(TaggedValue::F64(current_radius + net_delta), false),
|
||||
});
|
||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||
}
|
||||
}
|
||||
|
||||
fn calculate_circle_point_position(theta: f64, radius: f64) -> DVec2 {
|
||||
DVec2::new(radius * theta.cos(), -radius * theta.sin())
|
||||
}
|
||||
@@ -1,2 +1,4 @@
|
||||
pub mod circle_arc_radius_handle;
|
||||
pub mod number_of_points_dial;
|
||||
pub mod point_radius_handle;
|
||||
pub mod sweep_angle_gizmo;
|
||||
|
||||
+2
-2
@@ -189,8 +189,8 @@ impl NumberOfPointsDial {
|
||||
}
|
||||
|
||||
pub fn update_number_of_sides(&self, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque<Message>, drag_start: DVec2) {
|
||||
let delta = input.mouse.position - document.metadata().document_to_viewport.transform_point2(drag_start);
|
||||
let sign = (input.mouse.position.x - document.metadata().document_to_viewport.transform_point2(drag_start).x).signum();
|
||||
let delta = input.mouse.position - drag_start;
|
||||
let sign = (input.mouse.position.x - drag_start.x).signum();
|
||||
let net_delta = (delta.length() / 25.).round() * sign;
|
||||
|
||||
let Some(layer) = self.layer else { return };
|
||||
|
||||
+71
-57
@@ -142,14 +142,7 @@ impl PointRadiusHandle {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn overlays(
|
||||
&self,
|
||||
selected_star_layer: Option<LayerNodeIdentifier>,
|
||||
document: &DocumentMessageHandler,
|
||||
input: &InputPreprocessorMessageHandler,
|
||||
mouse_position: DVec2,
|
||||
overlay_context: &mut OverlayContext,
|
||||
) {
|
||||
pub fn overlays(&self, selected_star_layer: Option<LayerNodeIdentifier>, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, overlay_context: &mut OverlayContext) {
|
||||
match &self.handle_state {
|
||||
PointRadiusHandleState::Inactive => {
|
||||
let Some(layer) = selected_star_layer else { return };
|
||||
@@ -161,25 +154,12 @@ impl PointRadiusHandle {
|
||||
for i in 0..(2 * sides) {
|
||||
let point = star_vertex_position(viewport, i as i32, sides, radius1, radius2);
|
||||
let center = viewport.transform_point2(DVec2::ZERO);
|
||||
let viewport_diagonal = input.viewport_bounds.size().length();
|
||||
|
||||
// If the user zooms out such that shape is very small hide the gizmo
|
||||
if point.distance(center) < GIZMO_HIDE_THRESHOLD {
|
||||
return;
|
||||
}
|
||||
|
||||
if point.distance(mouse_position) < 5. {
|
||||
let Some(direction) = (point - center).try_normalize() else { continue };
|
||||
|
||||
overlay_context.manipulator_handle(point, true, None);
|
||||
let angle = ((i as f64) * PI) / (sides as f64);
|
||||
overlay_context.line(center, center + direction * viewport_diagonal, None, None);
|
||||
|
||||
draw_snapping_ticks(&self.snap_radii, direction, viewport, angle, overlay_context);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
overlay_context.manipulator_handle(point, false, None);
|
||||
}
|
||||
}
|
||||
@@ -191,22 +171,12 @@ impl PointRadiusHandle {
|
||||
for i in 0..sides {
|
||||
let point = polygon_vertex_position(viewport, i as i32, sides, radius);
|
||||
let center = viewport.transform_point2(DVec2::ZERO);
|
||||
let viewport_diagonal = input.viewport_bounds.size().length();
|
||||
|
||||
// If the user zooms out such that shape is very small hide the gizmo
|
||||
if point.distance(center) < GIZMO_HIDE_THRESHOLD {
|
||||
return;
|
||||
}
|
||||
|
||||
if point.distance(mouse_position) < 5. {
|
||||
let Some(direction) = (point - center).try_normalize() else { continue };
|
||||
|
||||
overlay_context.manipulator_handle(point, true, None);
|
||||
overlay_context.line(center, center + direction * viewport_diagonal, None, None);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
overlay_context.manipulator_handle(point, false, None);
|
||||
}
|
||||
}
|
||||
@@ -232,12 +202,9 @@ impl PointRadiusHandle {
|
||||
star_outline(Some(layer), document, overlay_context);
|
||||
|
||||
// Make the ticks for snapping
|
||||
|
||||
// If dragging to make radius negative don't show the
|
||||
if (mouse_position - center).dot(direction) < 0. {
|
||||
return;
|
||||
if (radius1.signum() * radius2.signum()).is_sign_positive() {
|
||||
draw_snapping_ticks(&self.snap_radii, direction, viewport, angle, overlay_context);
|
||||
}
|
||||
draw_snapping_ticks(&self.snap_radii, direction, viewport, angle, overlay_context);
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -262,7 +229,6 @@ impl PointRadiusHandle {
|
||||
};
|
||||
|
||||
let viewport = document.metadata().transform_to_viewport(layer);
|
||||
let center = viewport.transform_point2(DVec2::ZERO);
|
||||
|
||||
match snapping_index {
|
||||
// Make a triangle with previous two points
|
||||
@@ -274,41 +240,57 @@ impl PointRadiusHandle {
|
||||
overlay_context.line(before_outer_position, outer_position, Some(COLOR_OVERLAY_RED), Some(3.));
|
||||
overlay_context.line(outer_position, point_position, Some(COLOR_OVERLAY_RED), Some(3.));
|
||||
|
||||
let before_outer_position = viewport.inverse().transform_point2(before_outer_position);
|
||||
let outer_position = viewport.inverse().transform_point2(outer_position);
|
||||
let point_position = viewport.inverse().transform_point2(point_position);
|
||||
|
||||
let l1 = (before_outer_position - outer_position).length() * 0.2;
|
||||
let Some(l1_direction) = (before_outer_position - outer_position).try_normalize() else { return };
|
||||
let Some(l2_direction) = (point_position - outer_position).try_normalize() else { return };
|
||||
let Some(direction) = (center - outer_position).try_normalize() else { return };
|
||||
let Some(direction) = (-outer_position).try_normalize() else { return };
|
||||
|
||||
let new_point = SQRT_2 * l1 * direction + outer_position;
|
||||
|
||||
let before_outer_position = l1 * l1_direction + outer_position;
|
||||
let point_position = l1 * l2_direction + outer_position;
|
||||
|
||||
overlay_context.line(before_outer_position, new_point, Some(COLOR_OVERLAY_RED), Some(3.));
|
||||
overlay_context.line(new_point, point_position, Some(COLOR_OVERLAY_RED), Some(3.));
|
||||
overlay_context.line(
|
||||
viewport.transform_point2(before_outer_position),
|
||||
viewport.transform_point2(new_point),
|
||||
Some(COLOR_OVERLAY_RED),
|
||||
Some(3.),
|
||||
);
|
||||
overlay_context.line(viewport.transform_point2(new_point), viewport.transform_point2(point_position), Some(COLOR_OVERLAY_RED), Some(3.));
|
||||
}
|
||||
1 => {
|
||||
let before_outer_position = star_vertex_position(viewport, (self.point as i32) - 1, sides, radius1, radius2);
|
||||
|
||||
let after_point_position = star_vertex_position(viewport, (self.point as i32) + 1, sides, radius1, radius2);
|
||||
|
||||
let point_position = star_vertex_position(viewport, self.point as i32, sides, radius1, radius2);
|
||||
|
||||
overlay_context.line(before_outer_position, point_position, Some(COLOR_OVERLAY_RED), Some(3.));
|
||||
overlay_context.line(point_position, after_point_position, Some(COLOR_OVERLAY_RED), Some(3.));
|
||||
|
||||
let before_outer_position = viewport.inverse().transform_point2(before_outer_position);
|
||||
let after_point_position = viewport.inverse().transform_point2(after_point_position);
|
||||
let point_position = viewport.inverse().transform_point2(point_position);
|
||||
|
||||
let l1 = (before_outer_position - point_position).length() * 0.2;
|
||||
let Some(l1_direction) = (before_outer_position - point_position).try_normalize() else { return };
|
||||
let Some(l2_direction) = (after_point_position - point_position).try_normalize() else { return };
|
||||
let Some(direction) = (center - point_position).try_normalize() else { return };
|
||||
let Some(direction) = (-point_position).try_normalize() else { return };
|
||||
|
||||
let new_point = SQRT_2 * l1 * direction + point_position;
|
||||
|
||||
let before_outer_position = l1 * l1_direction + point_position;
|
||||
let after_point_position = l1 * l2_direction + point_position;
|
||||
|
||||
overlay_context.line(before_outer_position, new_point, Some(COLOR_OVERLAY_RED), Some(3.));
|
||||
overlay_context.line(new_point, after_point_position, Some(COLOR_OVERLAY_RED), Some(3.));
|
||||
overlay_context.line(
|
||||
viewport.transform_point2(before_outer_position),
|
||||
viewport.transform_point2(new_point),
|
||||
Some(COLOR_OVERLAY_RED),
|
||||
Some(3.),
|
||||
);
|
||||
overlay_context.line(viewport.transform_point2(new_point), viewport.transform_point2(after_point_position), Some(COLOR_OVERLAY_RED), Some(3.));
|
||||
}
|
||||
i => {
|
||||
// Use `self.point` as absolute reference as it matches the index of vertices of the star starting from 0
|
||||
@@ -353,25 +335,36 @@ impl PointRadiusHandle {
|
||||
return snap_radii;
|
||||
};
|
||||
|
||||
let other_index = if radius_index == 3 { 2 } else { 3 };
|
||||
|
||||
let Some(&TaggedValue::F64(other_radius)) = node_inputs[other_index].as_value() else {
|
||||
let (Some(&TaggedValue::F64(radius_1)), Some(&TaggedValue::F64(radius_2))) = (node_inputs[2].as_value(), node_inputs[3].as_value()) else {
|
||||
return snap_radii;
|
||||
};
|
||||
|
||||
let other_radius = if radius_index == 3 { radius_1 } else { radius_2 };
|
||||
|
||||
let Some(&TaggedValue::U32(sides)) = node_inputs[1].as_value() else {
|
||||
return snap_radii;
|
||||
};
|
||||
|
||||
let both_radii_negative = radius_1.is_sign_negative() && radius_2.is_sign_negative();
|
||||
let both_radii_same_sign = (radius_1.signum() * radius_2.signum()).is_sign_positive();
|
||||
|
||||
// When only one of the radii is negative, no need for snapping
|
||||
if !both_radii_same_sign {
|
||||
return snap_radii;
|
||||
}
|
||||
|
||||
let sign = if both_radii_negative { -1. } else { 1. };
|
||||
|
||||
// Inner radius for 90°
|
||||
let b = FRAC_PI_4 * 3. - PI / (sides as f64);
|
||||
let angle = b.sin();
|
||||
let required_radius = (other_radius / angle) * FRAC_1_SQRT_2;
|
||||
let required_radius = (other_radius.abs() * sign / angle) * FRAC_1_SQRT_2;
|
||||
|
||||
snap_radii.push(required_radius);
|
||||
|
||||
// Also push the case when the when it length increases more than the other
|
||||
|
||||
let flipped = other_radius * angle * SQRT_2;
|
||||
let flipped = other_radius.abs() * sign * angle * SQRT_2;
|
||||
|
||||
snap_radii.push(flipped);
|
||||
|
||||
@@ -386,11 +379,11 @@ impl PointRadiusHandle {
|
||||
break;
|
||||
}
|
||||
|
||||
if other_radius * factor > 1e-6 {
|
||||
snap_radii.push(other_radius * factor);
|
||||
if other_radius.abs() * factor > 1e-6 {
|
||||
snap_radii.push(other_radius.abs() * sign * factor);
|
||||
}
|
||||
|
||||
snap_radii.push((other_radius * 1.) / factor);
|
||||
snap_radii.push((other_radius.abs() * sign) / factor);
|
||||
}
|
||||
|
||||
snap_radii
|
||||
@@ -426,21 +419,23 @@ impl PointRadiusHandle {
|
||||
};
|
||||
|
||||
let viewport_transform = document.network_interface.document_metadata().transform_to_viewport(layer);
|
||||
let document_transform = document.network_interface.document_metadata().transform_to_document(layer);
|
||||
let center = viewport_transform.transform_point2(DVec2::ZERO);
|
||||
let radius_index = self.radius_index;
|
||||
|
||||
let original_radius = self.initial_radius;
|
||||
|
||||
let delta = viewport_transform.inverse().transform_point2(input.mouse.position) - document_transform.inverse().transform_point2(drag_start);
|
||||
let radius = document.metadata().document_to_viewport.transform_point2(drag_start) - center;
|
||||
let delta = viewport_transform.inverse().transform_point2(input.mouse.position) - viewport_transform.inverse().transform_point2(drag_start);
|
||||
let radius = drag_start - center;
|
||||
let projection = delta.project_onto(radius);
|
||||
let sign = radius.dot(delta).signum();
|
||||
|
||||
let mut net_delta = projection.length() * sign;
|
||||
let mut net_delta = projection.length() * sign * original_radius.signum();
|
||||
let new_radius = original_radius + net_delta;
|
||||
|
||||
self.update_state(PointRadiusHandleState::Dragging);
|
||||
|
||||
self.check_if_radius_flipped(original_radius, new_radius, document, layer, radius_index);
|
||||
|
||||
if let Some((index, snapped_delta)) = self.check_snapping(new_radius, original_radius) {
|
||||
net_delta = snapped_delta;
|
||||
self.update_state(PointRadiusHandleState::Snapped(index));
|
||||
@@ -452,4 +447,23 @@ impl PointRadiusHandle {
|
||||
});
|
||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||
}
|
||||
|
||||
fn check_if_radius_flipped(&mut self, original_radius: f64, new_radius: f64, document: &DocumentMessageHandler, layer: LayerNodeIdentifier, radius_index: usize) {
|
||||
let Some(node_inputs) = NodeGraphLayer::new(layer, &document.network_interface).find_node_inputs("Star") else {
|
||||
return;
|
||||
};
|
||||
|
||||
let (Some(&TaggedValue::F64(radius_1)), Some(&TaggedValue::F64(radius_2))) = (node_inputs[2].as_value(), node_inputs[3].as_value()) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let other_radius = if radius_index == 3 { radius_1 } else { radius_2 };
|
||||
|
||||
let flipped = (other_radius.is_sign_positive() && original_radius.is_sign_negative() && new_radius.is_sign_positive())
|
||||
|| (other_radius.is_sign_negative() && original_radius.is_sign_positive() && new_radius.is_sign_negative());
|
||||
|
||||
if flipped {
|
||||
self.snap_radii = Self::calculate_snap_radii(document, layer, radius_index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+365
@@ -0,0 +1,365 @@
|
||||
use crate::consts::{ARC_SNAP_THRESHOLD, GIZMO_HIDE_THRESHOLD};
|
||||
use crate::messages::message::Message;
|
||||
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::network_interface::InputConnector;
|
||||
use crate::messages::prelude::DocumentMessageHandler;
|
||||
use crate::messages::tool::common_functionality::graph_modification_utils;
|
||||
use crate::messages::tool::common_functionality::shapes::shape_utility::{arc_end_points, calculate_arc_text_transform, extract_arc_parameters, format_rounded};
|
||||
use crate::messages::tool::tool_messages::tool_prelude::*;
|
||||
use glam::DVec2;
|
||||
use graph_craft::document::value::TaggedValue;
|
||||
use graph_craft::document::{NodeId, NodeInput};
|
||||
use std::collections::VecDeque;
|
||||
use std::f64::consts::FRAC_PI_4;
|
||||
|
||||
#[derive(Clone, Debug, Default, PartialEq)]
|
||||
pub enum SweepAngleGizmoState {
|
||||
#[default]
|
||||
Inactive,
|
||||
Hover,
|
||||
Dragging,
|
||||
Snapped,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, PartialEq)]
|
||||
pub enum EndpointType {
|
||||
#[default]
|
||||
None,
|
||||
Start,
|
||||
End,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct SweepAngleGizmo {
|
||||
pub layer: Option<LayerNodeIdentifier>,
|
||||
endpoint: EndpointType,
|
||||
initial_start_angle: f64,
|
||||
initial_sweep_angle: f64,
|
||||
position_before_rotation: DVec2,
|
||||
previous_mouse_position: DVec2,
|
||||
total_angle_delta: f64,
|
||||
snap_angles: Vec<f64>,
|
||||
handle_state: SweepAngleGizmoState,
|
||||
}
|
||||
|
||||
impl SweepAngleGizmo {
|
||||
pub fn hovered(&self) -> bool {
|
||||
self.handle_state == SweepAngleGizmoState::Hover
|
||||
}
|
||||
|
||||
pub fn update_state(&mut self, state: SweepAngleGizmoState) {
|
||||
self.handle_state = state;
|
||||
}
|
||||
|
||||
pub fn is_dragging_or_snapped(&self) -> bool {
|
||||
self.handle_state == SweepAngleGizmoState::Dragging || self.handle_state == SweepAngleGizmoState::Snapped
|
||||
}
|
||||
|
||||
pub fn handle_actions(&mut self, layer: LayerNodeIdentifier, document: &DocumentMessageHandler, mouse_position: DVec2) {
|
||||
if self.handle_state == SweepAngleGizmoState::Inactive {
|
||||
let Some((start, end)) = arc_end_points(Some(layer), document) else { return };
|
||||
let Some((_, start_angle, sweep_angle, _)) = extract_arc_parameters(Some(layer), document) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let center = document.metadata().transform_to_viewport(layer).transform_point2(DVec2::ZERO);
|
||||
|
||||
if center.distance(start) < GIZMO_HIDE_THRESHOLD {
|
||||
return;
|
||||
}
|
||||
|
||||
let (close_to_gizmo, endpoint_type) = if mouse_position.distance(start) < 5. {
|
||||
(true, EndpointType::Start)
|
||||
} else if mouse_position.distance(end) < 5. {
|
||||
(true, EndpointType::End)
|
||||
} else {
|
||||
(false, EndpointType::None)
|
||||
};
|
||||
|
||||
if close_to_gizmo {
|
||||
self.layer = Some(layer);
|
||||
self.initial_start_angle = start_angle;
|
||||
self.initial_sweep_angle = sweep_angle;
|
||||
self.previous_mouse_position = mouse_position;
|
||||
self.total_angle_delta = 0.;
|
||||
self.position_before_rotation = if endpoint_type == EndpointType::End { end } else { start };
|
||||
self.endpoint = endpoint_type;
|
||||
self.snap_angles = Self::calculate_snap_angles();
|
||||
|
||||
self.update_state(SweepAngleGizmoState::Hover);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn overlays(
|
||||
&self,
|
||||
selected_arc_layer: Option<LayerNodeIdentifier>,
|
||||
document: &DocumentMessageHandler,
|
||||
_input: &InputPreprocessorMessageHandler,
|
||||
_mouse_position: DVec2,
|
||||
overlay_context: &mut OverlayContext,
|
||||
) {
|
||||
let tilt_offset = document.document_ptz.unmodified_tilt();
|
||||
|
||||
match self.handle_state {
|
||||
SweepAngleGizmoState::Inactive => {
|
||||
let Some((point1, point2)) = arc_end_points(selected_arc_layer, document) else { return };
|
||||
overlay_context.manipulator_handle(point1, false, None);
|
||||
overlay_context.manipulator_handle(point2, false, None);
|
||||
}
|
||||
SweepAngleGizmoState::Hover => {
|
||||
// Highlight the currently hovered endpoint only
|
||||
let Some((point1, point2)) = arc_end_points(self.layer, document) else { return };
|
||||
|
||||
let (point, other_point) = if self.endpoint == EndpointType::Start { (point1, point2) } else { (point2, point1) };
|
||||
overlay_context.manipulator_handle(point, true, None);
|
||||
overlay_context.manipulator_handle(other_point, false, None);
|
||||
}
|
||||
SweepAngleGizmoState::Dragging => {
|
||||
// Show snapping guides and angle arc while dragging
|
||||
let Some(layer) = self.layer else { return };
|
||||
let Some((current_start, current_end)) = arc_end_points(self.layer, document) else { return };
|
||||
let viewport = document.metadata().transform_to_viewport(layer);
|
||||
|
||||
// Depending on which endpoint is being dragged, draw guides relative to the static point
|
||||
let (point, other_point) = if self.endpoint == EndpointType::End {
|
||||
(current_end, current_start)
|
||||
} else {
|
||||
(current_start, current_end)
|
||||
};
|
||||
|
||||
// Draw the dashed line from center to drag start position
|
||||
overlay_context.dashed_line(self.position_before_rotation, viewport.transform_point2(DVec2::ZERO), None, None, Some(5.), Some(5.), Some(0.5));
|
||||
|
||||
overlay_context.manipulator_handle(other_point, false, None);
|
||||
|
||||
// Draw the angle, text and the bold line
|
||||
self.dragging_snapping_overlays(self.position_before_rotation, point, tilt_offset, viewport, overlay_context);
|
||||
}
|
||||
SweepAngleGizmoState::Snapped => {
|
||||
// When snapping is active, draw snapping arcs and angular guidelines
|
||||
let Some((start, end)) = arc_end_points(self.layer, document) else { return };
|
||||
let Some(layer) = self.layer else { return };
|
||||
let viewport = document.metadata().transform_to_viewport(layer);
|
||||
let center = viewport.transform_point2(DVec2::ZERO);
|
||||
|
||||
// Draw snapping arc and angle overlays between the two points
|
||||
let (a, b) = if self.endpoint == EndpointType::Start { (end, start) } else { (start, end) };
|
||||
self.dragging_snapping_overlays(a, b, tilt_offset, viewport, overlay_context);
|
||||
|
||||
// Draw lines from endpoints to the arc center
|
||||
overlay_context.line(start, center, None, Some(2.));
|
||||
overlay_context.line(end, center, None, Some(2.));
|
||||
|
||||
// Draw the line from drag start to arc center
|
||||
overlay_context.dashed_line(self.position_before_rotation, center, None, None, Some(5.), Some(5.), Some(0.5));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Draws the visual overlay during arc handle dragging or snapping interactions.
|
||||
/// This includes the dynamic arc sweep, angle label, and visual guides centered around the arc's origin.
|
||||
pub fn dragging_snapping_overlays(&self, initial_point: DVec2, final_point: DVec2, tilt_offset: f64, viewport: DAffine2, overlay_context: &mut OverlayContext) {
|
||||
let center = viewport.transform_point2(DVec2::ZERO);
|
||||
let initial_vector = initial_point - center;
|
||||
let final_vector = final_point - center;
|
||||
let offset_angle = initial_vector.to_angle() + tilt_offset;
|
||||
|
||||
let bold_radius = final_point.distance(center);
|
||||
|
||||
let angle = initial_vector.angle_to(final_vector).to_degrees();
|
||||
let display_angle = viewport
|
||||
.inverse()
|
||||
.transform_point2(final_point)
|
||||
.angle_to(viewport.inverse().transform_point2(initial_point))
|
||||
.to_degrees();
|
||||
|
||||
let text = format!("{}°", format_rounded(display_angle, 2));
|
||||
let text_texture_width = overlay_context.get_width(&text) / 2.;
|
||||
|
||||
let transform = calculate_arc_text_transform(angle, offset_angle, center, text_texture_width);
|
||||
|
||||
overlay_context.arc_sweep_angle(offset_angle, angle, final_point, bold_radius, center, &text, transform);
|
||||
}
|
||||
|
||||
pub fn update_arc(&mut self, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque<Message>) {
|
||||
let Some(layer) = self.layer else { return };
|
||||
let Some((_, current_start_angle, current_sweep_angle, _)) = extract_arc_parameters(Some(layer), document) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let viewport = document.metadata().transform_to_viewport(layer);
|
||||
let angle_delta = viewport
|
||||
.inverse()
|
||||
.transform_point2(self.previous_mouse_position)
|
||||
.angle_to(viewport.inverse().transform_point2(input.mouse.position))
|
||||
.to_degrees();
|
||||
let angle = self.total_angle_delta + angle_delta;
|
||||
|
||||
let Some(node_id) = graph_modification_utils::get_arc_id(layer, &document.network_interface) else {
|
||||
return;
|
||||
};
|
||||
|
||||
self.update_state(SweepAngleGizmoState::Dragging);
|
||||
|
||||
match self.endpoint {
|
||||
EndpointType::Start => {
|
||||
// Dragging start changes both start and sweep
|
||||
|
||||
let sign = -angle.signum();
|
||||
let mut total = angle;
|
||||
|
||||
let new_start_angle = self.initial_start_angle + total;
|
||||
let new_sweep_angle = self.initial_sweep_angle + total.abs() * sign;
|
||||
|
||||
match () {
|
||||
// Clamp sweep angle to 360°
|
||||
() if new_sweep_angle > 360. => {
|
||||
let wrapped = new_sweep_angle % 360.;
|
||||
self.total_angle_delta = -wrapped;
|
||||
|
||||
self.endpoint = EndpointType::End;
|
||||
|
||||
self.initial_sweep_angle = 360.;
|
||||
self.initial_start_angle = current_start_angle;
|
||||
self.update_state(SweepAngleGizmoState::Snapped);
|
||||
|
||||
self.apply_arc_update(node_id, self.initial_start_angle, self.initial_sweep_angle - wrapped, input, responses);
|
||||
}
|
||||
() if new_sweep_angle < 0. => {
|
||||
let rest_angle = angle_delta + new_sweep_angle;
|
||||
|
||||
self.total_angle_delta = new_sweep_angle.abs();
|
||||
self.endpoint = EndpointType::End;
|
||||
|
||||
self.initial_sweep_angle = 0.;
|
||||
self.initial_start_angle = current_start_angle + rest_angle;
|
||||
|
||||
self.apply_arc_update(node_id, self.initial_start_angle, new_sweep_angle.abs(), input, responses);
|
||||
}
|
||||
// Wrap start angle > 180° back into [-180°, 180°] and adjust sweep
|
||||
() if new_start_angle > 180. => {
|
||||
let overflow = new_start_angle % 180.;
|
||||
let rest_angle = angle_delta - overflow;
|
||||
|
||||
// We wrap the angle back into [-180°, 180°] range by jumping from +180° to -180°
|
||||
// Example: dragging past 190° becomes -170°, and we subtract the overshoot from sweep
|
||||
// Sweep angle must shrink to maintain consistent arc
|
||||
self.total_angle_delta = rest_angle;
|
||||
self.initial_start_angle = -180.;
|
||||
self.initial_sweep_angle = current_sweep_angle - rest_angle;
|
||||
|
||||
self.apply_arc_update(node_id, self.initial_start_angle + overflow, self.initial_sweep_angle - overflow, input, responses);
|
||||
}
|
||||
// Wrap start angle < -180° back into [-180°, 180°] and adjust sweep
|
||||
() if new_start_angle < -180. => {
|
||||
let underflow = new_start_angle % 180.;
|
||||
let rest_angle = angle_delta - underflow;
|
||||
|
||||
// We wrap the angle back into [-180°, 180°] by jumping from -190° to +170°
|
||||
// Sweep must grow to reflect continued clockwise drag past -180°
|
||||
// Start angle flips from -190° to +170°, and sweep increases accordingly
|
||||
self.total_angle_delta = underflow;
|
||||
self.initial_start_angle = 180.;
|
||||
self.initial_sweep_angle = current_sweep_angle + rest_angle.abs();
|
||||
|
||||
self.apply_arc_update(node_id, self.initial_start_angle + underflow, self.initial_sweep_angle + underflow.abs(), input, responses);
|
||||
}
|
||||
_ => {
|
||||
if let Some(snapped_delta) = self.check_snapping(self.initial_sweep_angle + total.abs() * sign) {
|
||||
total += snapped_delta;
|
||||
self.update_state(SweepAngleGizmoState::Snapped);
|
||||
}
|
||||
|
||||
self.total_angle_delta = angle;
|
||||
self.apply_arc_update(node_id, self.initial_start_angle + total, self.initial_sweep_angle + total.abs() * sign, input, responses);
|
||||
}
|
||||
}
|
||||
}
|
||||
EndpointType::End => {
|
||||
// Dragging the end only changes sweep angle
|
||||
|
||||
let mut total = angle;
|
||||
let new_sweep_angle = self.initial_sweep_angle + angle;
|
||||
|
||||
match () {
|
||||
// Clamp sweep angle below 0°, switch to start
|
||||
() if new_sweep_angle < 0. => {
|
||||
let delta = angle_delta - current_sweep_angle;
|
||||
let sign = -delta.signum();
|
||||
|
||||
self.initial_sweep_angle = 0.;
|
||||
self.total_angle_delta = delta;
|
||||
self.endpoint = EndpointType::Start;
|
||||
|
||||
self.apply_arc_update(node_id, self.initial_start_angle + delta, self.initial_sweep_angle + delta.abs() * sign, input, responses);
|
||||
}
|
||||
// Clamp sweep angle above 360°, switch to start
|
||||
() if new_sweep_angle > 360. => {
|
||||
let delta = angle_delta - (360. - new_sweep_angle);
|
||||
let sign = -delta.signum();
|
||||
|
||||
self.total_angle_delta = angle_delta - (360. - new_sweep_angle);
|
||||
self.initial_sweep_angle = 360.;
|
||||
self.endpoint = EndpointType::Start;
|
||||
self.update_state(SweepAngleGizmoState::Snapped);
|
||||
|
||||
self.apply_arc_update(node_id, self.initial_start_angle + angle_delta, self.initial_sweep_angle + angle_delta.abs() * sign, input, responses);
|
||||
}
|
||||
_ => {
|
||||
if let Some(snapped_delta) = self.check_snapping(self.initial_sweep_angle + angle) {
|
||||
total += snapped_delta;
|
||||
self.update_state(SweepAngleGizmoState::Snapped);
|
||||
}
|
||||
|
||||
self.total_angle_delta = angle;
|
||||
self.apply_arc_update(node_id, self.initial_start_angle, self.initial_sweep_angle + total, input, responses);
|
||||
}
|
||||
}
|
||||
}
|
||||
EndpointType::None => {}
|
||||
}
|
||||
}
|
||||
|
||||
/// Applies the updated start and sweep angles to the arc.
|
||||
fn apply_arc_update(&mut self, node_id: NodeId, start_angle: f64, sweep_angle: f64, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque<Message>) {
|
||||
self.snap_angles = Self::calculate_snap_angles();
|
||||
|
||||
responses.add(NodeGraphMessage::SetInput {
|
||||
input_connector: InputConnector::node(node_id, 2),
|
||||
input: NodeInput::value(TaggedValue::F64(start_angle), false),
|
||||
});
|
||||
responses.add(NodeGraphMessage::SetInput {
|
||||
input_connector: InputConnector::node(node_id, 3),
|
||||
input: NodeInput::value(TaggedValue::F64(sweep_angle), false),
|
||||
});
|
||||
|
||||
self.previous_mouse_position = input.mouse.position;
|
||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||
}
|
||||
|
||||
pub fn check_snapping(&self, new_sweep_angle: f64) -> Option<f64> {
|
||||
self.snap_angles.iter().find(|angle| (**angle - new_sweep_angle).abs() <= ARC_SNAP_THRESHOLD).map(|angle| {
|
||||
let delta = angle - new_sweep_angle;
|
||||
if self.endpoint == EndpointType::End { delta } else { -delta }
|
||||
})
|
||||
}
|
||||
|
||||
pub fn calculate_snap_angles() -> Vec<f64> {
|
||||
let mut snap_points = Vec::new();
|
||||
|
||||
for i in 0..=8 {
|
||||
let snap_point = i as f64 * FRAC_PI_4;
|
||||
snap_points.push(snap_point.to_degrees());
|
||||
}
|
||||
|
||||
snap_points
|
||||
}
|
||||
|
||||
pub fn cleanup(&mut self) {
|
||||
self.layer = None;
|
||||
self.endpoint = EndpointType::None;
|
||||
self.handle_state = SweepAngleGizmoState::Inactive;
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,6 @@ use crate::messages::portfolio::document::node_graph::document_node_definitions;
|
||||
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
||||
use crate::messages::portfolio::document::utility_types::network_interface::{FlowType, InputConnector, NodeNetworkInterface, NodeTemplate};
|
||||
use crate::messages::prelude::*;
|
||||
use bezier_rs::Subpath;
|
||||
use glam::DVec2;
|
||||
use graph_craft::document::value::TaggedValue;
|
||||
use graph_craft::document::{NodeId, NodeInput};
|
||||
@@ -11,10 +10,13 @@ use graph_craft::{ProtoNodeIdentifier, concrete};
|
||||
use graphene_std::Color;
|
||||
use graphene_std::NodeInputDecleration;
|
||||
use graphene_std::raster::BlendMode;
|
||||
use graphene_std::raster_types::{CPU, GPU, RasterDataTable};
|
||||
use graphene_std::raster_types::{CPU, GPU, Raster};
|
||||
use graphene_std::subpath::Subpath;
|
||||
use graphene_std::table::Table;
|
||||
use graphene_std::text::{Font, TypesettingConfig};
|
||||
use graphene_std::vector::style::Gradient;
|
||||
use graphene_std::vector::{ManipulatorPointId, PointId, SegmentId, VectorModificationType};
|
||||
use graphene_std::vector::misc::ManipulatorPointId;
|
||||
use graphene_std::vector::style::{Fill, Gradient};
|
||||
use graphene_std::vector::{PointId, SegmentId, VectorModificationType};
|
||||
use std::collections::VecDeque;
|
||||
|
||||
/// Returns the ID of the first Spline node in the horizontal flow which is not followed by a `Path` node, or `None` if none exists.
|
||||
@@ -33,7 +35,7 @@ pub fn merge_layers(document: &DocumentMessageHandler, first_layer: LayerNodeIde
|
||||
if first_layer == second_layer {
|
||||
return;
|
||||
}
|
||||
// Calculate the downstream transforms in order to bring the other vector data into the same layer space
|
||||
// Calculate the downstream transforms in order to bring the other vector geometry into the same layer space
|
||||
let first_layer_transform = document.metadata().downstream_transform_to_document(first_layer);
|
||||
let second_layer_transform = document.metadata().downstream_transform_to_document(second_layer);
|
||||
|
||||
@@ -153,31 +155,32 @@ pub fn merge_layers(document: &DocumentMessageHandler, first_layer: LayerNodeIde
|
||||
});
|
||||
|
||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||
responses.add(Message::StartBuffer);
|
||||
responses.add(PenToolMessage::RecalculateLatestPointsPosition);
|
||||
responses.add(DeferMessage::AfterGraphRun {
|
||||
messages: vec![PenToolMessage::RecalculateLatestPointsPosition.into()],
|
||||
});
|
||||
}
|
||||
|
||||
/// Merge the `first_endpoint` with `second_endpoint`.
|
||||
pub fn merge_points(document: &DocumentMessageHandler, layer: LayerNodeIdentifier, first_endpoint: PointId, second_endpont: PointId, responses: &mut VecDeque<Message>) {
|
||||
let transform = document.metadata().transform_to_document(layer);
|
||||
let Some(vector_data) = document.network_interface.compute_modified_vector(layer) else { return };
|
||||
let Some(vector) = document.network_interface.compute_modified_vector(layer) else { return };
|
||||
|
||||
let segment = vector_data.segment_bezier_iter().find(|(_, _, start, end)| *end == second_endpont || *start == second_endpont);
|
||||
let segment = vector.segment_bezier_iter().find(|(_, _, start, end)| *end == second_endpont || *start == second_endpont);
|
||||
let Some((segment, _, mut segment_start_point, mut segment_end_point)) = segment else {
|
||||
log::error!("Could not get the segment for second_endpoint.");
|
||||
return;
|
||||
};
|
||||
|
||||
let mut handles = [None; 2];
|
||||
if let Some(handle_position) = ManipulatorPointId::PrimaryHandle(segment).get_position(&vector_data) {
|
||||
let anchor_position = ManipulatorPointId::Anchor(segment_start_point).get_position(&vector_data).unwrap();
|
||||
if let Some(handle_position) = ManipulatorPointId::PrimaryHandle(segment).get_position(&vector) {
|
||||
let anchor_position = ManipulatorPointId::Anchor(segment_start_point).get_position(&vector).unwrap();
|
||||
let handle_position = transform.transform_point2(handle_position);
|
||||
let anchor_position = transform.transform_point2(anchor_position);
|
||||
let anchor_to_handle = handle_position - anchor_position;
|
||||
handles[0] = Some(anchor_to_handle);
|
||||
}
|
||||
if let Some(handle_position) = ManipulatorPointId::EndHandle(segment).get_position(&vector_data) {
|
||||
let anchor_position = ManipulatorPointId::Anchor(segment_end_point).get_position(&vector_data).unwrap();
|
||||
if let Some(handle_position) = ManipulatorPointId::EndHandle(segment).get_position(&vector) {
|
||||
let anchor_position = ManipulatorPointId::Anchor(segment_end_point).get_position(&vector).unwrap();
|
||||
let handle_position = transform.transform_point2(handle_position);
|
||||
let anchor_position = transform.transform_point2(anchor_position);
|
||||
let anchor_to_handle = handle_position - anchor_position;
|
||||
@@ -210,7 +213,7 @@ pub fn new_vector_layer(subpaths: Vec<Subpath<PointId>>, id: NodeId, parent: Lay
|
||||
}
|
||||
|
||||
/// Create a new bitmap layer.
|
||||
pub fn new_image_layer(image_frame: RasterDataTable<CPU>, id: NodeId, parent: LayerNodeIdentifier, responses: &mut VecDeque<Message>) -> LayerNodeIdentifier {
|
||||
pub fn new_image_layer(image_frame: Table<Raster<CPU>>, id: NodeId, parent: LayerNodeIdentifier, responses: &mut VecDeque<Message>) -> LayerNodeIdentifier {
|
||||
let insert_index = 0;
|
||||
responses.add(GraphOperationMessage::NewBitmapLayer {
|
||||
id,
|
||||
@@ -270,7 +273,7 @@ pub fn get_gradient(layer: LayerNodeIdentifier, network_interface: &NodeNetworkI
|
||||
let fill_index = 1;
|
||||
|
||||
let inputs = NodeGraphLayer::new(layer, network_interface).find_node_inputs("Fill")?;
|
||||
let TaggedValue::Fill(graphene_std::vector::style::Fill::Gradient(gradient)) = inputs.get(fill_index)?.as_value()? else {
|
||||
let TaggedValue::Fill(Fill::Gradient(gradient)) = inputs.get(fill_index)?.as_value()? else {
|
||||
return None;
|
||||
};
|
||||
Some(gradient.clone())
|
||||
@@ -281,7 +284,7 @@ pub fn get_fill_color(layer: LayerNodeIdentifier, network_interface: &NodeNetwor
|
||||
let fill_index = 1;
|
||||
|
||||
let inputs = NodeGraphLayer::new(layer, network_interface).find_node_inputs("Fill")?;
|
||||
let TaggedValue::Fill(graphene_std::vector::style::Fill::Solid(color)) = inputs.get(fill_index)?.as_value()? else {
|
||||
let TaggedValue::Fill(Fill::Solid(color)) = inputs.get(fill_index)?.as_value()? else {
|
||||
return None;
|
||||
};
|
||||
Some(color.to_linear_srgb())
|
||||
@@ -332,6 +335,10 @@ pub fn get_fill_id(layer: LayerNodeIdentifier, network_interface: &NodeNetworkIn
|
||||
NodeGraphLayer::new(layer, network_interface).upstream_node_id_from_name("Fill")
|
||||
}
|
||||
|
||||
pub fn get_circle_id(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<NodeId> {
|
||||
NodeGraphLayer::new(layer, network_interface).upstream_node_id_from_name("Circle")
|
||||
}
|
||||
|
||||
pub fn get_ellipse_id(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<NodeId> {
|
||||
NodeGraphLayer::new(layer, network_interface).upstream_node_id_from_name("Ellipse")
|
||||
}
|
||||
@@ -352,6 +359,10 @@ pub fn get_star_id(layer: LayerNodeIdentifier, network_interface: &NodeNetworkIn
|
||||
NodeGraphLayer::new(layer, network_interface).upstream_node_id_from_name("Star")
|
||||
}
|
||||
|
||||
pub fn get_arc_id(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<NodeId> {
|
||||
NodeGraphLayer::new(layer, network_interface).upstream_node_id_from_name("Arc")
|
||||
}
|
||||
|
||||
pub fn get_text_id(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<NodeId> {
|
||||
NodeGraphLayer::new(layer, network_interface).upstream_node_id_from_name("Text")
|
||||
}
|
||||
@@ -368,9 +379,8 @@ pub fn get_text(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInter
|
||||
let Some(&TaggedValue::OptionalF64(max_width)) = inputs[6].as_value() else { return None };
|
||||
let Some(&TaggedValue::OptionalF64(max_height)) = inputs[7].as_value() else { return None };
|
||||
let Some(&TaggedValue::F64(tilt)) = inputs[8].as_value() else { return None };
|
||||
let Some(TaggedValue::Bool(per_glyph_instances)) = &inputs[9].as_value() else {
|
||||
return None;
|
||||
};
|
||||
let Some(&TaggedValue::TextAlign(align)) = inputs[9].as_value() else { return None };
|
||||
let Some(&TaggedValue::Bool(per_glyph_instances)) = inputs[10].as_value() else { return None };
|
||||
|
||||
let typesetting = TypesettingConfig {
|
||||
font_size,
|
||||
@@ -379,8 +389,9 @@ pub fn get_text(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInter
|
||||
character_spacing,
|
||||
max_height,
|
||||
tilt,
|
||||
align,
|
||||
};
|
||||
Some((text, font, typesetting, *per_glyph_instances))
|
||||
Some((text, font, typesetting, per_glyph_instances))
|
||||
}
|
||||
|
||||
pub fn get_stroke_width(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<f64> {
|
||||
@@ -424,6 +435,16 @@ impl<'a> NodeGraphLayer<'a> {
|
||||
.find(|node_id| self.network_interface.reference(node_id, &[]).is_some_and(|reference| *reference == Some(node_name.to_string())))
|
||||
}
|
||||
|
||||
/// Node id of a visible node if it exists in the layer's primary flow until another layer
|
||||
pub fn upstream_visible_node_id_from_name_in_layer(&self, node_name: &str) -> Option<NodeId> {
|
||||
// `.skip(1)` is used to skip self
|
||||
self.horizontal_layer_flow()
|
||||
.skip(1)
|
||||
.take_while(|node_id| !self.network_interface.is_layer(node_id, &[]))
|
||||
.filter(|node_id| self.network_interface.is_visible(node_id, &[]))
|
||||
.find(|node_id| self.network_interface.reference(node_id, &[]).is_some_and(|reference| *reference == Some(node_name.to_string())))
|
||||
}
|
||||
|
||||
/// Node id of a protonode if it exists in the layer's primary flow
|
||||
pub fn upstream_node_id_from_protonode(&self, protonode_identifier: ProtoNodeIdentifier) -> Option<NodeId> {
|
||||
self.horizontal_layer_flow()
|
||||
@@ -438,10 +459,11 @@ impl<'a> NodeGraphLayer<'a> {
|
||||
|
||||
/// Find all of the inputs of a specific node within the layer's primary flow, up until the next layer is reached.
|
||||
pub fn find_node_inputs(&self, node_name: &str) -> Option<&'a Vec<NodeInput>> {
|
||||
// `.skip(1)` is used to skip self
|
||||
self.horizontal_layer_flow()
|
||||
.skip(1)// Skip self
|
||||
.take_while(|node_id| !self.network_interface.is_layer(node_id,&[]))
|
||||
.find(|node_id| self.network_interface.reference(node_id,&[]).is_some_and(|reference| *reference == Some(node_name.to_string())))
|
||||
.skip(1)
|
||||
.take_while(|node_id| !self.network_interface.is_layer(node_id, &[]))
|
||||
.find(|node_id| self.network_interface.reference(node_id, &[]).is_some_and(|reference| *reference == Some(node_name.to_string())))
|
||||
.and_then(|node_id| self.network_interface.document_network().nodes.get(&node_id).map(|node| &node.inputs))
|
||||
}
|
||||
|
||||
@@ -455,6 +477,6 @@ impl<'a> NodeGraphLayer<'a> {
|
||||
pub fn is_raster_layer(layer: LayerNodeIdentifier, network_interface: &mut NodeNetworkInterface) -> bool {
|
||||
let layer_input_type = network_interface.input_type(&InputConnector::node(layer.to_node(), 1), &[]).0.nested_type().clone();
|
||||
|
||||
layer_input_type == concrete!(RasterDataTable<CPU>) || layer_input_type == concrete!(RasterDataTable<GPU>)
|
||||
layer_input_type == concrete!(Table<Raster<CPU>>) || layer_input_type == concrete!(Table<Raster<GPU>>)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,18 +43,19 @@ fn draw_line_with_length(line_start: DVec2, line_end: DVec2, transform: DAffine2
|
||||
}
|
||||
}
|
||||
|
||||
/// Draws a dashed outline around a rectangle to visualize the AABB
|
||||
/// Draws a dashed outline around the given rectangle (assumed to be in document space).
|
||||
/// The provided transform is applied to convert coordinates (e.g., to viewport space) during rendering.
|
||||
fn draw_dashed_rect_outline(rect: Rect, transform: DAffine2, overlay_context: &mut OverlayContext) {
|
||||
let min = rect.min();
|
||||
let max = rect.max();
|
||||
|
||||
// Create the four corners of the rectangle
|
||||
let top_left = transform.transform_point2(DVec2::new(min.x, min.y));
|
||||
let top_right = transform.transform_point2(DVec2::new(max.x, min.y));
|
||||
let bottom_right = transform.transform_point2(DVec2::new(max.x, max.y));
|
||||
let bottom_left = transform.transform_point2(DVec2::new(min.x, max.y));
|
||||
// Define corners in document space
|
||||
let top_left = DVec2::new(min.x, min.y);
|
||||
let top_right = DVec2::new(max.x, min.y);
|
||||
let bottom_right = DVec2::new(max.x, max.y);
|
||||
let bottom_left = DVec2::new(min.x, max.y);
|
||||
|
||||
// Draw the four sides as dashed lines
|
||||
// Draw each edge using document-space coordinates; transform is applied inside draw_dashed_line
|
||||
draw_dashed_line(top_left, top_right, transform, overlay_context);
|
||||
draw_dashed_line(top_right, bottom_right, transform, overlay_context);
|
||||
draw_dashed_line(bottom_right, bottom_left, transform, overlay_context);
|
||||
|
||||
@@ -8,7 +8,8 @@ use crate::messages::tool::tool_messages::path_tool::PathOptionsUpdate;
|
||||
use crate::messages::tool::tool_messages::select_tool::SelectOptionsUpdate;
|
||||
use crate::messages::tool::tool_messages::tool_prelude::*;
|
||||
use glam::{DAffine2, DVec2};
|
||||
use graphene_std::{transform::ReferencePoint, vector::ManipulatorPointId};
|
||||
use graphene_std::transform::ReferencePoint;
|
||||
use graphene_std::vector::misc::ManipulatorPointId;
|
||||
use std::fmt;
|
||||
|
||||
pub fn pin_pivot_widget(active: bool, enabled: bool, source: PivotToolSource) -> WidgetHolder {
|
||||
@@ -16,8 +17,14 @@ pub fn pin_pivot_widget(active: bool, enabled: bool, source: PivotToolSource) ->
|
||||
.tooltip(String::from(if active { "Unpin Custom Pivot" } else { "Pin Custom Pivot" }) + "\n\nUnless pinned, the pivot will return to its prior reference point when a new selection is made.")
|
||||
.disabled(!enabled)
|
||||
.on_update(move |_| match source {
|
||||
PivotToolSource::Select => SelectToolMessage::SelectOptions(SelectOptionsUpdate::TogglePivotPinned).into(),
|
||||
PivotToolSource::Path => PathToolMessage::UpdateOptions(PathOptionsUpdate::TogglePivotPinned).into(),
|
||||
PivotToolSource::Select => SelectToolMessage::SelectOptions {
|
||||
options: SelectOptionsUpdate::TogglePivotPinned,
|
||||
}
|
||||
.into(),
|
||||
PivotToolSource::Path => PathToolMessage::UpdateOptions {
|
||||
options: PathOptionsUpdate::TogglePivotPinned,
|
||||
}
|
||||
.into(),
|
||||
})
|
||||
.widget_holder()
|
||||
}
|
||||
@@ -40,8 +47,14 @@ pub fn pivot_gizmo_type_widget(state: PivotGizmoState, source: PivotToolSource)
|
||||
MenuListEntry::new(format!("{gizmo_type:?}")).label(gizmo_type.to_string()).on_commit({
|
||||
let value = source.clone();
|
||||
move |_| match value {
|
||||
PivotToolSource::Select => SelectToolMessage::SelectOptions(SelectOptionsUpdate::PivotGizmoType(*gizmo_type)).into(),
|
||||
PivotToolSource::Path => PathToolMessage::UpdateOptions(PathOptionsUpdate::PivotGizmoType(*gizmo_type)).into(),
|
||||
PivotToolSource::Select => SelectToolMessage::SelectOptions {
|
||||
options: SelectOptionsUpdate::PivotGizmoType(*gizmo_type),
|
||||
}
|
||||
.into(),
|
||||
PivotToolSource::Path => PathToolMessage::UpdateOptions {
|
||||
options: PathOptionsUpdate::PivotGizmoType(*gizmo_type),
|
||||
}
|
||||
.into(),
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -56,8 +69,14 @@ pub fn pivot_gizmo_type_widget(state: PivotGizmoState, source: PivotToolSource)
|
||||
Disabled: rotation and scaling occurs about the center of the selection bounds.",
|
||||
)
|
||||
.on_update(move |optional_input: &CheckboxInput| match source {
|
||||
PivotToolSource::Select => SelectToolMessage::SelectOptions(SelectOptionsUpdate::TogglePivotGizmoType(optional_input.checked)).into(),
|
||||
PivotToolSource::Path => PathToolMessage::UpdateOptions(PathOptionsUpdate::TogglePivotGizmoType(optional_input.checked)).into(),
|
||||
PivotToolSource::Select => SelectToolMessage::SelectOptions {
|
||||
options: SelectOptionsUpdate::TogglePivotGizmoType(optional_input.checked),
|
||||
}
|
||||
.into(),
|
||||
PivotToolSource::Path => PathToolMessage::UpdateOptions {
|
||||
options: PathOptionsUpdate::TogglePivotGizmoType(optional_input.checked),
|
||||
}
|
||||
.into(),
|
||||
})
|
||||
.widget_holder(),
|
||||
Separator::new(SeparatorType::Related).widget_holder(),
|
||||
|
||||
@@ -48,56 +48,11 @@ impl Resize {
|
||||
/// Compute the drag start and end based on the current mouse position. Ignores the state of the layer.
|
||||
/// If you want to only draw whilst a layer exists, use [`Resize::calculate_points`].
|
||||
pub fn calculate_points_ignore_layer(&mut self, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, center: Key, lock_ratio: Key, in_document: bool) -> [DVec2; 2] {
|
||||
let start = self.viewport_drag_start(document);
|
||||
let mouse = input.mouse.position;
|
||||
let document_to_viewport = document.navigation_handler.calculate_offset_transform(input.viewport_bounds.center(), &document.document_ptz);
|
||||
let document_mouse = document_to_viewport.inverse().transform_point2(mouse);
|
||||
let mut points_viewport = [start, mouse];
|
||||
let ignore = if let Some(layer) = self.layer { vec![layer] } else { vec![] };
|
||||
let ratio = input.keyboard.get(lock_ratio as usize);
|
||||
let center = input.keyboard.get(center as usize);
|
||||
let snap_data = SnapData::ignore(document, input, &ignore);
|
||||
let config = SnapTypeConfiguration::default();
|
||||
if ratio {
|
||||
let viewport_size = points_viewport[1] - points_viewport[0];
|
||||
let raw_size = if in_document { document_to_viewport.inverse() } else { DAffine2::IDENTITY }.transform_vector2(viewport_size);
|
||||
let adjusted_size = raw_size.abs().max(raw_size.abs().yx()) * raw_size.signum();
|
||||
let size = if in_document { document_to_viewport.transform_vector2(adjusted_size) } else { adjusted_size };
|
||||
points_viewport[1] = points_viewport[0] + size;
|
||||
|
||||
let end_document = document_to_viewport.inverse().transform_point2(points_viewport[1]);
|
||||
let constraint = SnapConstraint::Line {
|
||||
origin: self.drag_start,
|
||||
direction: end_document - self.drag_start,
|
||||
};
|
||||
if center {
|
||||
let snapped = self.snap_manager.constrained_snap(&snap_data, &SnapCandidatePoint::handle(end_document), constraint, config);
|
||||
let far = SnapCandidatePoint::handle(2. * self.drag_start - end_document);
|
||||
let snapped_far = self.snap_manager.constrained_snap(&snap_data, &far, constraint, config);
|
||||
let best = if snapped_far.other_snap_better(&snapped) { snapped } else { snapped_far };
|
||||
points_viewport[0] = document_to_viewport.transform_point2(best.snapped_point_document);
|
||||
points_viewport[1] = document_to_viewport.transform_point2(self.drag_start * 2. - best.snapped_point_document);
|
||||
self.snap_manager.update_indicator(best);
|
||||
} else {
|
||||
let snapped = self.snap_manager.constrained_snap(&snap_data, &SnapCandidatePoint::handle(end_document), constraint, config);
|
||||
points_viewport[1] = document_to_viewport.transform_point2(snapped.snapped_point_document);
|
||||
self.snap_manager.update_indicator(snapped);
|
||||
}
|
||||
} else if center {
|
||||
let snapped = self.snap_manager.free_snap(&snap_data, &SnapCandidatePoint::handle(document_mouse), config);
|
||||
let opposite = 2. * self.drag_start - document_mouse;
|
||||
let snapped_far = self.snap_manager.free_snap(&snap_data, &SnapCandidatePoint::handle(opposite), config);
|
||||
let best = if snapped_far.other_snap_better(&snapped) { snapped } else { snapped_far };
|
||||
points_viewport[0] = document_to_viewport.transform_point2(best.snapped_point_document);
|
||||
points_viewport[1] = document_to_viewport.transform_point2(self.drag_start * 2. - best.snapped_point_document);
|
||||
self.snap_manager.update_indicator(best);
|
||||
} else {
|
||||
let snapped = self.snap_manager.free_snap(&snap_data, &SnapCandidatePoint::handle(document_mouse), config);
|
||||
points_viewport[1] = document_to_viewport.transform_point2(snapped.snapped_point_document);
|
||||
self.snap_manager.update_indicator(snapped);
|
||||
}
|
||||
|
||||
points_viewport
|
||||
// Use shared snapping logic with optional center and ratio constraints, considering if coordinates are in document space.
|
||||
self.compute_snapped_resize_points(document, input, center, ratio, in_document)
|
||||
}
|
||||
|
||||
pub fn calculate_transform(&mut self, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, center: Key, lock_ratio: Key, skip_rerender: bool) -> Option<Message> {
|
||||
@@ -113,6 +68,81 @@ impl Resize {
|
||||
)
|
||||
}
|
||||
|
||||
pub fn calculate_circle_points(&mut self, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, center: Key) -> [DVec2; 2] {
|
||||
let center = input.keyboard.get(center as usize);
|
||||
|
||||
// Use shared snapping logic with enforced aspect ratio and optional center snapping.
|
||||
self.compute_snapped_resize_points(document, input, center, true, false)
|
||||
}
|
||||
|
||||
/// Calculates two points in viewport space from a drag, applying snapping, optional center mode, and aspect ratio locking.
|
||||
fn compute_snapped_resize_points(&mut self, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, center: bool, lock_ratio: bool, in_document: bool) -> [DVec2; 2] {
|
||||
let start = self.viewport_drag_start(document);
|
||||
let mouse = input.mouse.position;
|
||||
let document_to_viewport = document.navigation_handler.calculate_offset_transform(input.viewport_bounds.center(), &document.document_ptz);
|
||||
let drag_start = self.drag_start;
|
||||
let mut points_viewport = [start, mouse];
|
||||
|
||||
let ignore = if let Some(layer) = self.layer { vec![layer] } else { vec![] };
|
||||
let snap_data = &SnapData::ignore(document, input, &ignore);
|
||||
|
||||
if lock_ratio {
|
||||
let viewport_size = points_viewport[1] - points_viewport[0];
|
||||
let raw_size = if in_document {
|
||||
document_to_viewport.inverse().transform_vector2(viewport_size)
|
||||
} else {
|
||||
viewport_size
|
||||
};
|
||||
|
||||
let adjusted_size = raw_size.abs().max(raw_size.abs().yx()) * raw_size.signum();
|
||||
let size = if in_document { document_to_viewport.transform_vector2(adjusted_size) } else { adjusted_size };
|
||||
|
||||
points_viewport[1] = points_viewport[0] + size;
|
||||
let end_document = document_to_viewport.inverse().transform_point2(points_viewport[1]);
|
||||
let constraint = SnapConstraint::Line {
|
||||
origin: drag_start,
|
||||
direction: end_document - drag_start,
|
||||
};
|
||||
|
||||
if center {
|
||||
let snapped = self
|
||||
.snap_manager
|
||||
.constrained_snap(snap_data, &SnapCandidatePoint::handle(end_document), constraint, SnapTypeConfiguration::default());
|
||||
let far = SnapCandidatePoint::handle(2. * drag_start - end_document);
|
||||
let snapped_far = self.snap_manager.constrained_snap(snap_data, &far, constraint, SnapTypeConfiguration::default());
|
||||
let best = if snapped_far.other_snap_better(&snapped) { snapped } else { snapped_far };
|
||||
|
||||
points_viewport[0] = document_to_viewport.transform_point2(best.snapped_point_document);
|
||||
points_viewport[1] = document_to_viewport.transform_point2(drag_start * 2. - best.snapped_point_document);
|
||||
self.snap_manager.update_indicator(best);
|
||||
} else {
|
||||
let snapped = self
|
||||
.snap_manager
|
||||
.constrained_snap(snap_data, &SnapCandidatePoint::handle(end_document), constraint, SnapTypeConfiguration::default());
|
||||
points_viewport[1] = document_to_viewport.transform_point2(snapped.snapped_point_document);
|
||||
self.snap_manager.update_indicator(snapped);
|
||||
}
|
||||
} else {
|
||||
let document_mouse = document_to_viewport.inverse().transform_point2(mouse);
|
||||
if center {
|
||||
let snapped = self.snap_manager.free_snap(snap_data, &SnapCandidatePoint::handle(document_mouse), SnapTypeConfiguration::default());
|
||||
let opposite = 2. * drag_start - document_mouse;
|
||||
let snapped_far = self.snap_manager.free_snap(snap_data, &SnapCandidatePoint::handle(opposite), SnapTypeConfiguration::default());
|
||||
let best = if snapped_far.other_snap_better(&snapped) { snapped } else { snapped_far };
|
||||
|
||||
points_viewport[0] = document_to_viewport.transform_point2(best.snapped_point_document);
|
||||
points_viewport[1] = document_to_viewport.transform_point2(drag_start * 2. - best.snapped_point_document);
|
||||
self.snap_manager.update_indicator(best);
|
||||
} else {
|
||||
let snapped = self.snap_manager.free_snap(snap_data, &SnapCandidatePoint::handle(document_mouse), SnapTypeConfiguration::default());
|
||||
points_viewport[1] = document_to_viewport.transform_point2(snapped.snapped_point_document);
|
||||
self.snap_manager.update_indicator(snapped);
|
||||
}
|
||||
}
|
||||
|
||||
points_viewport
|
||||
}
|
||||
|
||||
pub fn cleanup(&mut self, responses: &mut VecDeque<Message>) {
|
||||
self.snap_manager.cleanup(responses);
|
||||
self.layer = None;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,186 @@
|
||||
use super::shape_utility::ShapeToolModifierKey;
|
||||
use super::*;
|
||||
use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn;
|
||||
use crate::messages::portfolio::document::node_graph::document_node_definitions::resolve_document_node_type;
|
||||
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
||||
use crate::messages::portfolio::document::utility_types::network_interface::{InputConnector, NodeTemplate};
|
||||
use crate::messages::tool::common_functionality::gizmos::shape_gizmos::circle_arc_radius_handle::{RadiusHandle, RadiusHandleState};
|
||||
use crate::messages::tool::common_functionality::gizmos::shape_gizmos::sweep_angle_gizmo::{SweepAngleGizmo, SweepAngleGizmoState};
|
||||
use crate::messages::tool::common_functionality::graph_modification_utils;
|
||||
use crate::messages::tool::common_functionality::shapes::shape_utility::{ShapeGizmoHandler, arc_outline};
|
||||
use crate::messages::tool::tool_messages::tool_prelude::*;
|
||||
use glam::DAffine2;
|
||||
use graph_craft::document::NodeInput;
|
||||
use graph_craft::document::value::TaggedValue;
|
||||
use graphene_std::vector::misc::ArcType;
|
||||
use std::collections::VecDeque;
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct ArcGizmoHandler {
|
||||
sweep_angle_gizmo: SweepAngleGizmo,
|
||||
arc_radius_handle: RadiusHandle,
|
||||
}
|
||||
|
||||
impl ArcGizmoHandler {
|
||||
pub fn new() -> Self {
|
||||
Self { ..Default::default() }
|
||||
}
|
||||
}
|
||||
|
||||
impl ShapeGizmoHandler for ArcGizmoHandler {
|
||||
fn handle_state(&mut self, selected_shape_layer: LayerNodeIdentifier, mouse_position: DVec2, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) {
|
||||
self.sweep_angle_gizmo.handle_actions(selected_shape_layer, document, mouse_position);
|
||||
self.arc_radius_handle.handle_actions(selected_shape_layer, document, mouse_position, responses);
|
||||
}
|
||||
|
||||
fn is_any_gizmo_hovered(&self) -> bool {
|
||||
self.sweep_angle_gizmo.hovered() || self.arc_radius_handle.hovered()
|
||||
}
|
||||
|
||||
fn handle_click(&mut self) {
|
||||
// If hovering over both the gizmos give priority to sweep angle gizmo
|
||||
if self.sweep_angle_gizmo.hovered() && self.arc_radius_handle.hovered() {
|
||||
self.sweep_angle_gizmo.update_state(SweepAngleGizmoState::Dragging);
|
||||
self.arc_radius_handle.update_state(RadiusHandleState::Inactive);
|
||||
return;
|
||||
}
|
||||
|
||||
if self.sweep_angle_gizmo.hovered() {
|
||||
self.sweep_angle_gizmo.update_state(SweepAngleGizmoState::Dragging);
|
||||
}
|
||||
|
||||
if self.arc_radius_handle.hovered() {
|
||||
self.arc_radius_handle.update_state(RadiusHandleState::Dragging);
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_update(&mut self, drag_start: DVec2, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque<Message>) {
|
||||
if self.sweep_angle_gizmo.is_dragging_or_snapped() {
|
||||
self.sweep_angle_gizmo.update_arc(document, input, responses);
|
||||
}
|
||||
|
||||
if self.arc_radius_handle.is_dragging() {
|
||||
self.arc_radius_handle.update_inner_radius(document, input, responses, drag_start);
|
||||
}
|
||||
}
|
||||
|
||||
fn dragging_overlays(
|
||||
&self,
|
||||
document: &DocumentMessageHandler,
|
||||
input: &InputPreprocessorMessageHandler,
|
||||
_shape_editor: &mut &mut crate::messages::tool::common_functionality::shape_editor::ShapeState,
|
||||
mouse_position: DVec2,
|
||||
overlay_context: &mut crate::messages::portfolio::document::overlays::utility_types::OverlayContext,
|
||||
) {
|
||||
if self.sweep_angle_gizmo.is_dragging_or_snapped() {
|
||||
self.sweep_angle_gizmo.overlays(None, document, input, mouse_position, overlay_context);
|
||||
arc_outline(self.sweep_angle_gizmo.layer, document, overlay_context);
|
||||
}
|
||||
|
||||
if self.arc_radius_handle.is_dragging() {
|
||||
self.sweep_angle_gizmo.overlays(self.arc_radius_handle.layer, document, input, mouse_position, overlay_context);
|
||||
self.arc_radius_handle.overlays(document, overlay_context);
|
||||
}
|
||||
}
|
||||
|
||||
fn overlays(
|
||||
&self,
|
||||
document: &DocumentMessageHandler,
|
||||
selected_shape_layer: Option<LayerNodeIdentifier>,
|
||||
input: &InputPreprocessorMessageHandler,
|
||||
_shape_editor: &mut &mut crate::messages::tool::common_functionality::shape_editor::ShapeState,
|
||||
mouse_position: DVec2,
|
||||
overlay_context: &mut crate::messages::portfolio::document::overlays::utility_types::OverlayContext,
|
||||
) {
|
||||
// If hovering over both the gizmos give priority to sweep angle gizmo
|
||||
if self.sweep_angle_gizmo.hovered() && self.arc_radius_handle.hovered() {
|
||||
self.sweep_angle_gizmo.overlays(selected_shape_layer, document, input, mouse_position, overlay_context);
|
||||
return;
|
||||
}
|
||||
|
||||
if self.arc_radius_handle.hovered() {
|
||||
let layer = self.arc_radius_handle.layer;
|
||||
|
||||
self.arc_radius_handle.overlays(document, overlay_context);
|
||||
self.sweep_angle_gizmo.overlays(layer, document, input, mouse_position, overlay_context);
|
||||
}
|
||||
|
||||
self.sweep_angle_gizmo.overlays(selected_shape_layer, document, input, mouse_position, overlay_context);
|
||||
self.arc_radius_handle.overlays(document, overlay_context);
|
||||
|
||||
arc_outline(selected_shape_layer.or(self.sweep_angle_gizmo.layer), document, overlay_context);
|
||||
}
|
||||
|
||||
fn mouse_cursor_icon(&self) -> Option<MouseCursorIcon> {
|
||||
if self.sweep_angle_gizmo.hovered() || self.sweep_angle_gizmo.is_dragging_or_snapped() {
|
||||
return Some(MouseCursorIcon::Default);
|
||||
}
|
||||
|
||||
if self.arc_radius_handle.hovered() || self.arc_radius_handle.is_dragging() {
|
||||
return Some(MouseCursorIcon::EWResize);
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn cleanup(&mut self) {
|
||||
self.sweep_angle_gizmo.cleanup();
|
||||
self.arc_radius_handle.cleanup();
|
||||
}
|
||||
}
|
||||
#[derive(Default)]
|
||||
pub struct Arc;
|
||||
|
||||
impl Arc {
|
||||
pub fn create_node(arc_type: ArcType) -> NodeTemplate {
|
||||
let node_type = resolve_document_node_type("Arc").expect("Ellipse node does not exist");
|
||||
node_type.node_template_input_override([
|
||||
None,
|
||||
Some(NodeInput::value(TaggedValue::F64(0.5), false)),
|
||||
Some(NodeInput::value(TaggedValue::F64(0.), false)),
|
||||
Some(NodeInput::value(TaggedValue::F64(270.), false)),
|
||||
Some(NodeInput::value(TaggedValue::ArcType(arc_type), false)),
|
||||
])
|
||||
}
|
||||
|
||||
pub fn update_shape(
|
||||
document: &DocumentMessageHandler,
|
||||
ipp: &InputPreprocessorMessageHandler,
|
||||
layer: LayerNodeIdentifier,
|
||||
shape_tool_data: &mut ShapeToolData,
|
||||
modifier: ShapeToolModifierKey,
|
||||
responses: &mut VecDeque<Message>,
|
||||
) {
|
||||
let (center, lock_ratio) = (modifier[0], modifier[1]);
|
||||
if let Some([start, end]) = shape_tool_data.data.calculate_points(document, ipp, center, lock_ratio) {
|
||||
let Some(node_id) = graph_modification_utils::get_arc_id(layer, &document.network_interface) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let dimensions = (start - end).abs();
|
||||
let mut scale = DVec2::ONE;
|
||||
let radius: f64;
|
||||
|
||||
// We keep the smaller dimension's scale at 1 and scale the other dimension accordingly
|
||||
if dimensions.x > dimensions.y {
|
||||
scale.x = dimensions.x / dimensions.y;
|
||||
radius = dimensions.y / 2.;
|
||||
} else {
|
||||
scale.y = dimensions.y / dimensions.x;
|
||||
radius = dimensions.x / 2.;
|
||||
}
|
||||
|
||||
responses.add(NodeGraphMessage::SetInput {
|
||||
input_connector: InputConnector::node(node_id, 1),
|
||||
input: NodeInput::value(TaggedValue::F64(radius), false),
|
||||
});
|
||||
|
||||
responses.add(GraphOperationMessage::TransformSet {
|
||||
layer,
|
||||
transform: DAffine2::from_scale_angle_translation(scale, 0., start.midpoint(end)),
|
||||
transform_in: TransformIn::Viewport,
|
||||
skip_rerender: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn;
|
||||
use crate::messages::portfolio::document::node_graph::document_node_definitions::resolve_document_node_type;
|
||||
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::network_interface::{InputConnector, NodeTemplate};
|
||||
use crate::messages::tool::common_functionality::gizmos::shape_gizmos::circle_arc_radius_handle::{RadiusHandle, RadiusHandleState};
|
||||
use crate::messages::tool::common_functionality::graph_modification_utils;
|
||||
use crate::messages::tool::common_functionality::shape_editor::ShapeState;
|
||||
use crate::messages::tool::common_functionality::shapes::shape_utility::{ShapeGizmoHandler, ShapeToolModifierKey};
|
||||
use crate::messages::tool::tool_messages::shape_tool::ShapeToolData;
|
||||
use crate::messages::tool::tool_messages::tool_prelude::*;
|
||||
use glam::DAffine2;
|
||||
use graph_craft::document::NodeInput;
|
||||
use graph_craft::document::value::TaggedValue;
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct CircleGizmoHandler {
|
||||
circle_radius_handle: RadiusHandle,
|
||||
}
|
||||
|
||||
impl ShapeGizmoHandler for CircleGizmoHandler {
|
||||
fn is_any_gizmo_hovered(&self) -> bool {
|
||||
self.circle_radius_handle.hovered()
|
||||
}
|
||||
|
||||
fn handle_state(&mut self, selected_circle_layer: LayerNodeIdentifier, mouse_position: DVec2, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) {
|
||||
self.circle_radius_handle.handle_actions(selected_circle_layer, document, mouse_position, responses);
|
||||
}
|
||||
|
||||
fn handle_click(&mut self) {
|
||||
if self.circle_radius_handle.hovered() {
|
||||
self.circle_radius_handle.update_state(RadiusHandleState::Dragging);
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_update(&mut self, drag_start: DVec2, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque<Message>) {
|
||||
if self.circle_radius_handle.is_dragging() {
|
||||
self.circle_radius_handle.update_inner_radius(document, input, responses, drag_start);
|
||||
}
|
||||
}
|
||||
|
||||
fn overlays(
|
||||
&self,
|
||||
document: &DocumentMessageHandler,
|
||||
_selected_circle_layer: Option<LayerNodeIdentifier>,
|
||||
_input: &InputPreprocessorMessageHandler,
|
||||
_shape_editor: &mut &mut ShapeState,
|
||||
_mouse_position: DVec2,
|
||||
overlay_context: &mut OverlayContext,
|
||||
) {
|
||||
self.circle_radius_handle.overlays(document, overlay_context);
|
||||
}
|
||||
|
||||
fn dragging_overlays(
|
||||
&self,
|
||||
document: &DocumentMessageHandler,
|
||||
_input: &InputPreprocessorMessageHandler,
|
||||
_shape_editor: &mut &mut ShapeState,
|
||||
_mouse_position: DVec2,
|
||||
overlay_context: &mut OverlayContext,
|
||||
) {
|
||||
if self.circle_radius_handle.is_dragging() {
|
||||
self.circle_radius_handle.overlays(document, overlay_context);
|
||||
}
|
||||
}
|
||||
|
||||
fn cleanup(&mut self) {
|
||||
self.circle_radius_handle.cleanup();
|
||||
}
|
||||
|
||||
fn mouse_cursor_icon(&self) -> Option<MouseCursorIcon> {
|
||||
if self.circle_radius_handle.hovered() || self.circle_radius_handle.is_dragging() {
|
||||
return Some(MouseCursorIcon::EWResize);
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Circle;
|
||||
|
||||
impl Circle {
|
||||
pub fn create_node() -> NodeTemplate {
|
||||
let node_type = resolve_document_node_type("Circle").expect("Circle can't be found");
|
||||
node_type.node_template_input_override([None, Some(NodeInput::value(TaggedValue::F64(0.), false))])
|
||||
}
|
||||
|
||||
pub fn update_shape(
|
||||
document: &DocumentMessageHandler,
|
||||
ipp: &InputPreprocessorMessageHandler,
|
||||
layer: LayerNodeIdentifier,
|
||||
shape_tool_data: &mut ShapeToolData,
|
||||
modifier: ShapeToolModifierKey,
|
||||
responses: &mut VecDeque<Message>,
|
||||
) {
|
||||
let center = modifier[0];
|
||||
let [start, end] = shape_tool_data.data.calculate_circle_points(document, ipp, center);
|
||||
let Some(node_id) = graph_modification_utils::get_circle_id(layer, &document.network_interface) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let dimensions = (start - end).abs();
|
||||
|
||||
// We keep the smaller dimension's scale at 1 and scale the other dimension accordingly
|
||||
let radius: f64 = if dimensions.x > dimensions.y { dimensions.y / 2. } else { dimensions.x / 2. };
|
||||
|
||||
responses.add(NodeGraphMessage::SetInput {
|
||||
input_connector: InputConnector::node(node_id, 1),
|
||||
input: NodeInput::value(TaggedValue::F64(radius), false),
|
||||
});
|
||||
|
||||
responses.add(GraphOperationMessage::TransformSet {
|
||||
layer,
|
||||
transform: DAffine2::from_scale_angle_translation(DVec2::ONE, 0., start.midpoint(end)),
|
||||
transform_in: TransformIn::Viewport,
|
||||
skip_rerender: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -28,7 +28,7 @@ impl Ellipse {
|
||||
modifier: ShapeToolModifierKey,
|
||||
responses: &mut VecDeque<Message>,
|
||||
) {
|
||||
let [center, lock_ratio, _, _] = modifier;
|
||||
let [center, lock_ratio, _] = modifier;
|
||||
|
||||
if let Some([start, end]) = shape_tool_data.data.calculate_points(document, ipp, center, lock_ratio) {
|
||||
let Some(node_id) = graph_modification_utils::get_ellipse_id(layer, &document.network_interface) else {
|
||||
|
||||
@@ -53,7 +53,7 @@ impl Line {
|
||||
modifier: ShapeToolModifierKey,
|
||||
responses: &mut VecDeque<Message>,
|
||||
) {
|
||||
let [center, _, lock_angle, snap_angle] = modifier;
|
||||
let [center, snap_angle, lock_angle] = modifier;
|
||||
|
||||
shape_tool_data.line_data.drag_current = ipp.mouse.position;
|
||||
|
||||
@@ -210,18 +210,18 @@ mod test_line_tool {
|
||||
async fn get_line_node_inputs(editor: &mut EditorTestUtils) -> Option<(DVec2, DVec2)> {
|
||||
let document = editor.active_document();
|
||||
let network_interface = &document.network_interface;
|
||||
let node_id = network_interface
|
||||
|
||||
network_interface
|
||||
.selected_nodes()
|
||||
.selected_visible_and_unlocked_layers(network_interface)
|
||||
.filter_map(|layer| {
|
||||
let node_inputs = NodeGraphLayer::new(layer, &network_interface).find_node_inputs("Line")?;
|
||||
let node_inputs = NodeGraphLayer::new(layer, network_interface).find_node_inputs("Line")?;
|
||||
let (Some(&TaggedValue::DVec2(start)), Some(&TaggedValue::DVec2(end))) = (node_inputs[1].as_value(), node_inputs[2].as_value()) else {
|
||||
return None;
|
||||
};
|
||||
Some((start, end))
|
||||
})
|
||||
.next();
|
||||
node_id
|
||||
.next()
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -245,11 +245,7 @@ mod test_line_tool {
|
||||
editor.new_document().await;
|
||||
editor.handle_message(NavigationMessage::CanvasZoomSet { zoom_factor: 2. }).await;
|
||||
editor.handle_message(NavigationMessage::CanvasPan { delta: DVec2::new(100., 50.) }).await;
|
||||
editor
|
||||
.handle_message(NavigationMessage::CanvasTiltSet {
|
||||
angle_radians: (30. as f64).to_radians(),
|
||||
})
|
||||
.await;
|
||||
editor.handle_message(NavigationMessage::CanvasTiltSet { angle_radians: 30_f64.to_radians() }).await;
|
||||
editor.drag_tool(ToolType::Line, 0., 0., 100., 100., ModifierKeys::empty()).await;
|
||||
if let Some((start_input, end_input)) = get_line_node_inputs(&mut editor).await {
|
||||
let document = editor.active_document();
|
||||
@@ -261,15 +257,11 @@ mod test_line_tool {
|
||||
|
||||
assert!(
|
||||
(start_input - expected_start).length() < 1.,
|
||||
"Start point should match expected document coordinates. Got {:?}, expected {:?}",
|
||||
start_input,
|
||||
expected_start
|
||||
"Start point should match expected document coordinates. Got {start_input:?}, expected {expected_start:?}"
|
||||
);
|
||||
assert!(
|
||||
(end_input - expected_end).length() < 1.,
|
||||
"End point should match expected document coordinates. Got {:?}, expected {:?}",
|
||||
end_input,
|
||||
expected_end
|
||||
"End point should match expected document coordinates. Got {end_input:?}, expected {expected_end:?}"
|
||||
);
|
||||
} else {
|
||||
panic!("Line was not created successfully with transformed viewport");
|
||||
@@ -282,27 +274,19 @@ mod test_line_tool {
|
||||
editor.new_document().await;
|
||||
editor.drag_tool(ToolType::Line, 0., 0., 100., 100., ModifierKeys::CONTROL).await;
|
||||
if let Some((start_input, end_input)) = get_line_node_inputs(&mut editor).await {
|
||||
match (start_input, end_input) {
|
||||
(start_input, end_input) => {
|
||||
let line_vec = end_input - start_input;
|
||||
let original_angle = line_vec.angle_to(DVec2::X);
|
||||
editor.drag_tool(ToolType::Line, 0., 0., 200., 50., ModifierKeys::CONTROL).await;
|
||||
if let Some((updated_start, updated_end)) = get_line_node_inputs(&mut editor).await {
|
||||
match (updated_start, updated_end) {
|
||||
(updated_start, updated_end) => {
|
||||
let updated_line_vec = updated_end - updated_start;
|
||||
let updated_angle = updated_line_vec.angle_to(DVec2::X);
|
||||
print!("{:?}", original_angle);
|
||||
print!("{:?}", updated_angle);
|
||||
assert!(
|
||||
line_vec.normalize().dot(updated_line_vec.normalize()).abs() - 1. < 1e-6,
|
||||
"Line angle should be locked when Ctrl is kept pressed"
|
||||
);
|
||||
assert!((updated_start - updated_end).length() > 1., "Line should be able to change length when Ctrl is kept pressed");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let line_vec = end_input - start_input;
|
||||
let original_angle = line_vec.angle_to(DVec2::X);
|
||||
editor.drag_tool(ToolType::Line, 0., 0., 200., 50., ModifierKeys::CONTROL).await;
|
||||
if let Some((updated_start, updated_end)) = get_line_node_inputs(&mut editor).await {
|
||||
let updated_line_vec = updated_end - updated_start;
|
||||
let updated_angle = updated_line_vec.angle_to(DVec2::X);
|
||||
print!("{original_angle:?}");
|
||||
print!("{updated_angle:?}");
|
||||
assert!(
|
||||
line_vec.normalize().dot(updated_line_vec.normalize()).abs() - 1. < 1e-6,
|
||||
"Line angle should be locked when Ctrl is kept pressed"
|
||||
);
|
||||
assert!((updated_start - updated_end).length() > 1., "Line should be able to change length when Ctrl is kept pressed");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -313,14 +297,10 @@ mod test_line_tool {
|
||||
editor.new_document().await;
|
||||
editor.drag_tool(ToolType::Line, 100., 100., 200., 100., ModifierKeys::ALT).await;
|
||||
if let Some((start_input, end_input)) = get_line_node_inputs(&mut editor).await {
|
||||
match (start_input, end_input) {
|
||||
(start_input, end_input) => {
|
||||
let expected_start = DVec2::new(0., 100.);
|
||||
let expected_end = DVec2::new(200., 100.);
|
||||
assert!((start_input - expected_start).length() < 1., "Start point should be near (0, 100)");
|
||||
assert!((end_input - expected_end).length() < 1., "End point should be near (200, 100)");
|
||||
}
|
||||
}
|
||||
let expected_start = DVec2::new(0., 100.);
|
||||
let expected_end = DVec2::new(200., 100.);
|
||||
assert!((start_input - expected_start).length() < 1., "Start point should be near (0, 100)");
|
||||
assert!((end_input - expected_end).length() < 1., "End point should be near (200, 100)");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
pub mod arc_shape;
|
||||
pub mod circle_shape;
|
||||
pub mod ellipse_shape;
|
||||
pub mod line_shape;
|
||||
pub mod polygon_shape;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user