mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-21 04:28:12 +08:00
Merge branch 'master' into masking
This commit is contained in:
@@ -61,6 +61,7 @@ pub const SELECTION_DRAG_ANGLE: f64 = 90.;
|
||||
pub const PIVOT_CROSSHAIR_THICKNESS: f64 = 1.;
|
||||
pub const PIVOT_CROSSHAIR_LENGTH: f64 = 9.;
|
||||
pub const PIVOT_DIAMETER: f64 = 5.;
|
||||
pub const DOWEL_PIN_RADIUS: f64 = 4.;
|
||||
|
||||
// COMPASS ROSE
|
||||
pub const COMPASS_ROSE_RING_INNER_DIAMETER: f64 = 13.;
|
||||
@@ -133,8 +134,8 @@ pub const SCALE_EFFECT: f64 = 0.5;
|
||||
|
||||
// COLORS
|
||||
pub const COLOR_OVERLAY_BLUE: &str = "#00a8ff";
|
||||
pub const COLOR_OVERLAY_BLUE_50: &str = "rgba(0, 168, 255, 0.5)";
|
||||
pub const COLOR_OVERLAY_YELLOW: &str = "#ffc848";
|
||||
pub const COLOR_OVERLAY_YELLOW_DULL: &str = "#d7ba8b";
|
||||
pub const COLOR_OVERLAY_GREEN: &str = "#63ce63";
|
||||
pub const COLOR_OVERLAY_RED: &str = "#ef5454";
|
||||
pub const COLOR_OVERLAY_GRAY: &str = "#cccccc";
|
||||
|
||||
@@ -140,6 +140,7 @@ impl Dispatcher {
|
||||
let graphene_std::renderer::RenderMetadata {
|
||||
upstream_footprints: footprints,
|
||||
local_transforms,
|
||||
first_instance_source_id,
|
||||
click_targets,
|
||||
clip_targets,
|
||||
} = render_metadata;
|
||||
@@ -149,6 +150,7 @@ impl Dispatcher {
|
||||
DocumentMessage::UpdateUpstreamTransforms {
|
||||
upstream_footprints: footprints,
|
||||
local_transforms,
|
||||
first_instance_source_id,
|
||||
},
|
||||
DocumentMessage::UpdateClickTargets { click_targets },
|
||||
DocumentMessage::UpdateClipTargets { clip_targets },
|
||||
|
||||
@@ -15,3 +15,4 @@ pub mod node_graph_executor;
|
||||
#[cfg(test)]
|
||||
pub mod test_utils;
|
||||
pub mod utility_traits;
|
||||
pub mod utility_types;
|
||||
|
||||
@@ -24,7 +24,7 @@ enum AnimationState {
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, PartialEq)]
|
||||
#[derive(Default, Debug, Clone, PartialEq, ExtractField)]
|
||||
pub struct AnimationMessageHandler {
|
||||
/// Used to re-send the UI on the next frame after playback starts
|
||||
live_preview_recently_zero: bool,
|
||||
@@ -57,6 +57,7 @@ impl AnimationMessageHandler {
|
||||
}
|
||||
}
|
||||
|
||||
#[message_handler_data]
|
||||
impl MessageHandler<AnimationMessage, ()> for AnimationMessageHandler {
|
||||
fn process_message(&mut self, message: AnimationMessage, responses: &mut VecDeque<Message>, _data: ()) {
|
||||
match message {
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
use crate::messages::prelude::*;
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
#[derive(Debug, Clone, Default, ExtractField)]
|
||||
pub struct BroadcastMessageHandler {
|
||||
listeners: HashMap<BroadcastEvent, Vec<Message>>,
|
||||
}
|
||||
|
||||
#[message_handler_data]
|
||||
impl MessageHandler<BroadcastMessage, ()> for BroadcastMessageHandler {
|
||||
fn process_message(&mut self, message: BroadcastMessage, responses: &mut VecDeque<Message>, _data: ()) {
|
||||
match message {
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
use super::utility_types::MessageLoggingVerbosity;
|
||||
use crate::messages::prelude::*;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
#[derive(Debug, Default, ExtractField)]
|
||||
pub struct DebugMessageHandler {
|
||||
pub message_logging_verbosity: MessageLoggingVerbosity,
|
||||
}
|
||||
|
||||
#[message_handler_data]
|
||||
impl MessageHandler<DebugMessage, ()> for DebugMessageHandler {
|
||||
fn process_message(&mut self, message: DebugMessage, responses: &mut VecDeque<Message>, _data: ()) {
|
||||
match message {
|
||||
|
||||
@@ -2,19 +2,21 @@ use super::simple_dialogs::{self, AboutGraphiteDialog, ComingSoonDialog, DemoArt
|
||||
use crate::messages::layout::utility_types::widget_prelude::*;
|
||||
use crate::messages::prelude::*;
|
||||
|
||||
#[derive(ExtractField)]
|
||||
pub struct DialogMessageData<'a> {
|
||||
pub portfolio: &'a PortfolioMessageHandler,
|
||||
pub preferences: &'a PreferencesMessageHandler,
|
||||
}
|
||||
|
||||
/// Stores the dialogs which require state. These are the ones that have their own message handlers, and are not the ones defined in `simple_dialogs`.
|
||||
#[derive(Debug, Default, Clone)]
|
||||
#[derive(Debug, Default, Clone, ExtractField)]
|
||||
pub struct DialogMessageHandler {
|
||||
export_dialog: ExportDialogMessageHandler,
|
||||
new_document_dialog: NewDocumentDialogMessageHandler,
|
||||
preferences_dialog: PreferencesDialogMessageHandler,
|
||||
}
|
||||
|
||||
#[message_handler_data]
|
||||
impl MessageHandler<DialogMessage, DialogMessageData<'_>> for DialogMessageHandler {
|
||||
fn process_message(&mut self, message: DialogMessage, responses: &mut VecDeque<Message>, data: DialogMessageData) {
|
||||
let DialogMessageData { portfolio, preferences } = data;
|
||||
|
||||
@@ -3,12 +3,13 @@ use crate::messages::layout::utility_types::widget_prelude::*;
|
||||
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
||||
use crate::messages::prelude::*;
|
||||
|
||||
#[derive(ExtractField)]
|
||||
pub struct ExportDialogMessageData<'a> {
|
||||
pub portfolio: &'a PortfolioMessageHandler,
|
||||
}
|
||||
|
||||
/// A dialog to allow users to customize their file export.
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, ExtractField)]
|
||||
pub struct ExportDialogMessageHandler {
|
||||
pub file_type: FileType,
|
||||
pub scale_factor: f64,
|
||||
@@ -31,6 +32,7 @@ impl Default for ExportDialogMessageHandler {
|
||||
}
|
||||
}
|
||||
|
||||
#[message_handler_data]
|
||||
impl MessageHandler<ExportDialogMessage, ExportDialogMessageData<'_>> for ExportDialogMessageHandler {
|
||||
fn process_message(&mut self, message: ExportDialogMessage, responses: &mut VecDeque<Message>, data: ExportDialogMessageData) {
|
||||
let ExportDialogMessageData { portfolio } = data;
|
||||
|
||||
+2
-1
@@ -4,13 +4,14 @@ use glam::{IVec2, UVec2};
|
||||
use graph_craft::document::NodeId;
|
||||
|
||||
/// A dialog to allow users to set some initial options about a new document.
|
||||
#[derive(Debug, Clone, Default)]
|
||||
#[derive(Debug, Clone, Default, ExtractField)]
|
||||
pub struct NewDocumentDialogMessageHandler {
|
||||
pub name: String,
|
||||
pub infinite: bool,
|
||||
pub dimensions: UVec2,
|
||||
}
|
||||
|
||||
#[message_handler_data]
|
||||
impl MessageHandler<NewDocumentDialogMessage, ()> for NewDocumentDialogMessageHandler {
|
||||
fn process_message(&mut self, message: NewDocumentDialogMessage, responses: &mut VecDeque<Message>, _data: ()) {
|
||||
match message {
|
||||
|
||||
@@ -4,14 +4,16 @@ use crate::messages::portfolio::document::utility_types::wires::GraphWireStyle;
|
||||
use crate::messages::preferences::SelectionMode;
|
||||
use crate::messages::prelude::*;
|
||||
|
||||
#[derive(ExtractField)]
|
||||
pub struct PreferencesDialogMessageData<'a> {
|
||||
pub preferences: &'a PreferencesMessageHandler,
|
||||
}
|
||||
|
||||
/// A dialog to allow users to customize Graphite editor options
|
||||
#[derive(Debug, Clone, Default)]
|
||||
#[derive(Debug, Clone, Default, ExtractField)]
|
||||
pub struct PreferencesDialogMessageHandler {}
|
||||
|
||||
#[message_handler_data]
|
||||
impl MessageHandler<PreferencesDialogMessage, PreferencesDialogMessageData<'_>> for PreferencesDialogMessageHandler {
|
||||
fn process_message(&mut self, message: PreferencesDialogMessage, responses: &mut VecDeque<Message>, data: PreferencesDialogMessageData) {
|
||||
let PreferencesDialogMessageData { preferences } = data;
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
use crate::messages::prelude::*;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
#[derive(Debug, Default, ExtractField)]
|
||||
pub struct GlobalsMessageHandler {}
|
||||
|
||||
#[message_handler_data]
|
||||
impl MessageHandler<GlobalsMessage, ()> for GlobalsMessageHandler {
|
||||
fn process_message(&mut self, message: GlobalsMessage, _responses: &mut VecDeque<Message>, _data: ()) {
|
||||
match message {
|
||||
|
||||
@@ -6,16 +6,18 @@ use crate::messages::portfolio::utility_types::KeyboardPlatformLayout;
|
||||
use crate::messages::prelude::*;
|
||||
use std::fmt::Write;
|
||||
|
||||
#[derive(ExtractField)]
|
||||
pub struct InputMapperMessageData<'a> {
|
||||
pub input: &'a InputPreprocessorMessageHandler,
|
||||
pub actions: ActionList,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
#[derive(Debug, Default, ExtractField)]
|
||||
pub struct InputMapperMessageHandler {
|
||||
mapping: Mapping,
|
||||
}
|
||||
|
||||
#[message_handler_data]
|
||||
impl MessageHandler<InputMapperMessage, InputMapperMessageData<'_>> for InputMapperMessageHandler {
|
||||
fn process_message(&mut self, message: InputMapperMessage, responses: &mut VecDeque<Message>, data: InputMapperMessageData) {
|
||||
let InputMapperMessageData { input, actions } = data;
|
||||
|
||||
@@ -2,16 +2,18 @@ use crate::messages::input_mapper::input_mapper_message_handler::InputMapperMess
|
||||
use crate::messages::input_mapper::utility_types::input_keyboard::KeysGroup;
|
||||
use crate::messages::prelude::*;
|
||||
|
||||
#[derive(ExtractField)]
|
||||
pub struct KeyMappingMessageData<'a> {
|
||||
pub input: &'a InputPreprocessorMessageHandler,
|
||||
pub actions: ActionList,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
#[derive(Debug, Default, ExtractField)]
|
||||
pub struct KeyMappingMessageHandler {
|
||||
mapping_handler: InputMapperMessageHandler,
|
||||
}
|
||||
|
||||
#[message_handler_data]
|
||||
impl MessageHandler<KeyMappingMessage, KeyMappingMessageData<'_>> for KeyMappingMessageHandler {
|
||||
fn process_message(&mut self, message: KeyMappingMessage, responses: &mut VecDeque<Message>, data: KeyMappingMessageData) {
|
||||
let KeyMappingMessageData { input, actions } = data;
|
||||
|
||||
@@ -6,11 +6,12 @@ use crate::messages::prelude::*;
|
||||
use glam::DVec2;
|
||||
use std::time::Duration;
|
||||
|
||||
#[derive(ExtractField)]
|
||||
pub struct InputPreprocessorMessageData {
|
||||
pub keyboard_platform: KeyboardPlatformLayout,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
#[derive(Debug, Default, ExtractField)]
|
||||
pub struct InputPreprocessorMessageHandler {
|
||||
pub frame_time: FrameTimeInfo,
|
||||
pub time: u64,
|
||||
@@ -19,6 +20,7 @@ pub struct InputPreprocessorMessageHandler {
|
||||
pub viewport_bounds: ViewportBounds,
|
||||
}
|
||||
|
||||
#[message_handler_data]
|
||||
impl MessageHandler<InputPreprocessorMessage, InputPreprocessorMessageData> for InputPreprocessorMessageHandler {
|
||||
fn process_message(&mut self, message: InputPreprocessorMessage, responses: &mut VecDeque<Message>, data: InputPreprocessorMessageData) {
|
||||
let InputPreprocessorMessageData { keyboard_platform } = data;
|
||||
|
||||
@@ -6,7 +6,7 @@ use graphene_std::text::Font;
|
||||
use graphene_std::vector::style::{FillChoice, GradientStops};
|
||||
use serde_json::Value;
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
#[derive(Debug, Clone, Default, ExtractField)]
|
||||
pub struct LayoutMessageHandler {
|
||||
layouts: [Layout; LayoutTarget::LayoutTargetLength as usize],
|
||||
}
|
||||
@@ -342,6 +342,15 @@ impl LayoutMessageHandler {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn custom_data() -> MessageData {
|
||||
// TODO: When <https://github.com/dtolnay/proc-macro2/issues/503> is resolved and released,
|
||||
// TODO: use <https://doc.rust-lang.org/stable/proc_macro/struct.Span.html#method.line> to get
|
||||
// TODO: the line number instead of hardcoding it to the magic number on the following line.
|
||||
// TODO: Also, utilize the line number in the actual output, since it is currently unused.
|
||||
MessageData::new(String::from("Function"), vec![(String::from("Fn(&MessageDiscriminant) -> Option<KeysGroup>"), 350)], file!())
|
||||
}
|
||||
|
||||
#[message_handler_data(CustomData)]
|
||||
impl<F: Fn(&MessageDiscriminant) -> Option<KeysGroup>> MessageHandler<LayoutMessage, F> for LayoutMessageHandler {
|
||||
fn process_message(&mut self, message: LayoutMessage, responses: &mut std::collections::VecDeque<Message>, action_input_mapping: F) {
|
||||
match message {
|
||||
|
||||
@@ -471,6 +471,8 @@ pub struct ReferencePointInput {
|
||||
|
||||
pub disabled: bool,
|
||||
|
||||
pub tooltip: String,
|
||||
|
||||
// Callbacks
|
||||
#[serde(skip)]
|
||||
#[derivative(Debug = "ignore", PartialEq = "ignore")]
|
||||
|
||||
@@ -45,3 +45,86 @@ impl specta::Type for MessageDiscriminant {
|
||||
specta::DataType::Any
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use std::io::Write;
|
||||
|
||||
#[test]
|
||||
fn generate_message_tree() {
|
||||
let result = Message::build_message_tree();
|
||||
let mut file = std::fs::File::create("../hierarchical_message_system_tree.txt").unwrap();
|
||||
file.write_all(format!("{} `{}`\n", result.name(), result.path()).as_bytes()).unwrap();
|
||||
if let Some(variants) = result.variants() {
|
||||
for (i, variant) in variants.iter().enumerate() {
|
||||
let is_last = i == variants.len() - 1;
|
||||
print_tree_node(variant, "", is_last, &mut file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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))
|
||||
} else {
|
||||
if is_last {
|
||||
("└── ", format!("{} ", prefix))
|
||||
} else {
|
||||
("├── ", format!("{}│ ", prefix))
|
||||
}
|
||||
};
|
||||
|
||||
if tree.path().is_empty() {
|
||||
file.write_all(format!("{}{}{}\n", prefix, branch, tree.name()).as_bytes()).unwrap();
|
||||
} else {
|
||||
file.write_all(format!("{}{}{} `{}`\n", prefix, branch, tree.name(), tree.path()).as_bytes()).unwrap();
|
||||
}
|
||||
|
||||
// Print children if any
|
||||
if let Some(variants) = tree.variants() {
|
||||
let len = variants.len();
|
||||
for (i, variant) in variants.iter().enumerate() {
|
||||
let is_last_child = i == len - 1;
|
||||
print_tree_node(variant, &child_prefix, is_last_child, file);
|
||||
}
|
||||
}
|
||||
|
||||
// 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() {
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
// Print data field if any
|
||||
if let Some(data) = tree.message_handler_data_fields() {
|
||||
let len = data.fields().len();
|
||||
if data.path().is_empty() {
|
||||
file.write_all(format!("{}{}{}\n", prefix, "└── ", data.name()).as_bytes()).unwrap();
|
||||
} else {
|
||||
file.write_all(format!("{}{}{} `{}`\n", prefix, "└── ", 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", format!("{} ", prefix), branch, field.0).as_bytes()).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -182,6 +182,7 @@ pub enum DocumentMessage {
|
||||
UpdateUpstreamTransforms {
|
||||
upstream_footprints: HashMap<NodeId, Footprint>,
|
||||
local_transforms: HashMap<NodeId, DAffine2>,
|
||||
first_instance_source_id: HashMap<NodeId, Option<NodeId>>,
|
||||
},
|
||||
UpdateClickTargets {
|
||||
click_targets: HashMap<NodeId, Vec<ClickTarget>>,
|
||||
|
||||
@@ -38,6 +38,7 @@ use graphene_std::vector::click_target::{ClickTarget, ClickTargetType};
|
||||
use graphene_std::vector::style::ViewMode;
|
||||
use std::time::Duration;
|
||||
|
||||
#[derive(ExtractField)]
|
||||
pub struct DocumentMessageData<'a> {
|
||||
pub document_id: DocumentId,
|
||||
pub ipp: &'a InputPreprocessorMessageHandler,
|
||||
@@ -48,7 +49,7 @@ pub struct DocumentMessageData<'a> {
|
||||
pub device_pixel_ratio: f64,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize, ExtractField)]
|
||||
#[serde(default)]
|
||||
pub struct DocumentMessageHandler {
|
||||
// ======================
|
||||
@@ -168,6 +169,7 @@ impl Default for DocumentMessageHandler {
|
||||
}
|
||||
}
|
||||
|
||||
#[message_handler_data]
|
||||
impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessageHandler {
|
||||
fn process_message(&mut self, message: DocumentMessage, responses: &mut VecDeque<Message>, data: DocumentMessageData) {
|
||||
let DocumentMessageData {
|
||||
@@ -750,6 +752,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
|
||||
// Nudge translation without resizing
|
||||
if !resize {
|
||||
let transform = DAffine2::from_translation(DVec2::from_angle(-self.document_ptz.tilt()).rotate(DVec2::new(delta_x, delta_y)));
|
||||
responses.add(SelectToolMessage::ShiftSelectedNodes { offset: transform.translation });
|
||||
|
||||
for layer in self.network_interface.shallowest_unique_layers(&[]).filter(|layer| can_move(*layer)) {
|
||||
responses.add(GraphOperationMessage::TransformChange {
|
||||
@@ -1185,6 +1188,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
|
||||
OverlaysType::HoverOutline => visibility_settings.hover_outline = visible,
|
||||
OverlaysType::SelectionOutline => visibility_settings.selection_outline = visible,
|
||||
OverlaysType::Pivot => visibility_settings.pivot = visible,
|
||||
OverlaysType::Origin => visibility_settings.origin = visible,
|
||||
OverlaysType::Path => visibility_settings.path = visible,
|
||||
OverlaysType::Anchors => {
|
||||
visibility_settings.anchors = visible;
|
||||
@@ -1305,8 +1309,10 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
|
||||
DocumentMessage::UpdateUpstreamTransforms {
|
||||
upstream_footprints,
|
||||
local_transforms,
|
||||
first_instance_source_id,
|
||||
} => {
|
||||
self.network_interface.update_transforms(upstream_footprints, local_transforms);
|
||||
self.network_interface.update_first_instance_source_id(first_instance_source_id);
|
||||
}
|
||||
DocumentMessage::UpdateClickTargets { click_targets } => {
|
||||
// TODO: Allow non layer nodes to have click targets
|
||||
@@ -1714,6 +1720,14 @@ impl DocumentMessageHandler {
|
||||
.reduce(graphene_std::renderer::Quad::combine_bounds)
|
||||
}
|
||||
|
||||
pub fn selected_visible_and_unlock_layers_bounding_box_document(&self) -> Option<[DVec2; 2]> {
|
||||
self.network_interface
|
||||
.selected_nodes()
|
||||
.selected_visible_and_unlocked_layers(&self.network_interface)
|
||||
.map(|layer| self.metadata().nonzero_bounding_box(layer))
|
||||
.reduce(graphene_std::renderer::Quad::combine_bounds)
|
||||
}
|
||||
|
||||
pub fn document_network(&self) -> &NodeNetwork {
|
||||
self.network_interface.document_network()
|
||||
}
|
||||
@@ -2267,6 +2281,24 @@ impl DocumentMessageHandler {
|
||||
]
|
||||
},
|
||||
},
|
||||
LayoutGroup::Row {
|
||||
widgets: {
|
||||
let mut checkbox_id = CheckboxId::default();
|
||||
vec![
|
||||
CheckboxInput::new(self.overlays_visibility_settings.pivot)
|
||||
.on_update(|optional_input: &CheckboxInput| {
|
||||
DocumentMessage::SetOverlaysVisibility {
|
||||
visible: optional_input.checked,
|
||||
overlays_type: Some(OverlaysType::Origin),
|
||||
}
|
||||
.into()
|
||||
})
|
||||
.for_label(checkbox_id.clone())
|
||||
.widget_holder(),
|
||||
TextLabel::new("Transform Origin".to_string()).for_checkbox(&mut checkbox_id).widget_holder(),
|
||||
]
|
||||
},
|
||||
},
|
||||
LayoutGroup::Row {
|
||||
widgets: {
|
||||
let mut checkbox_id = CheckboxId::default();
|
||||
|
||||
@@ -3,7 +3,7 @@ use crate::messages::portfolio::document::utility_types::document_metadata::Laye
|
||||
use crate::messages::portfolio::document::utility_types::network_interface::NodeTemplate;
|
||||
use crate::messages::prelude::*;
|
||||
use bezier_rs::Subpath;
|
||||
use glam::{DAffine2, DVec2, IVec2};
|
||||
use glam::{DAffine2, IVec2};
|
||||
use graph_craft::document::NodeId;
|
||||
use graphene_std::Artboard;
|
||||
use graphene_std::brush::brush_stroke::BrushStroke;
|
||||
@@ -52,10 +52,6 @@ pub enum GraphOperationMessage {
|
||||
transform_in: TransformIn,
|
||||
skip_rerender: bool,
|
||||
},
|
||||
TransformSetPivot {
|
||||
layer: LayerNodeIdentifier,
|
||||
pivot: DVec2,
|
||||
},
|
||||
Vector {
|
||||
layer: LayerNodeIdentifier,
|
||||
modification_type: VectorModificationType,
|
||||
|
||||
+3
-10
@@ -21,17 +21,19 @@ struct ArtboardInfo {
|
||||
merge_node: NodeId,
|
||||
}
|
||||
|
||||
#[derive(ExtractField)]
|
||||
pub struct GraphOperationMessageData<'a> {
|
||||
pub network_interface: &'a mut NodeNetworkInterface,
|
||||
pub collapsed: &'a mut CollapsedLayers,
|
||||
pub node_graph: &'a mut NodeGraphMessageHandler,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Default, serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Debug, Clone, PartialEq, Default, serde::Serialize, serde::Deserialize, ExtractField)]
|
||||
pub struct GraphOperationMessageHandler {}
|
||||
|
||||
// GraphOperationMessageHandler always modified the document network. This is so changes to the layers panel will only affect the document network.
|
||||
// For changes to the selected network, use NodeGraphMessageHandler. No NodeGraphMessage's should be added here, since they will affect the selected nested network.
|
||||
#[message_handler_data]
|
||||
impl MessageHandler<GraphOperationMessage, GraphOperationMessageData<'_>> for GraphOperationMessageHandler {
|
||||
fn process_message(&mut self, message: GraphOperationMessage, responses: &mut VecDeque<Message>, data: GraphOperationMessageData) {
|
||||
let network_interface = data.network_interface;
|
||||
@@ -89,15 +91,6 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageData<'_>> for Gr
|
||||
modify_inputs.transform_set(transform, transform_in, skip_rerender);
|
||||
}
|
||||
}
|
||||
GraphOperationMessage::TransformSetPivot { layer, pivot } => {
|
||||
if layer == LayerNodeIdentifier::ROOT_PARENT {
|
||||
log::error!("Cannot run TransformSetPivot on ROOT_PARENT");
|
||||
return;
|
||||
}
|
||||
if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer, network_interface, responses) {
|
||||
modify_inputs.pivot_set(pivot);
|
||||
}
|
||||
}
|
||||
GraphOperationMessage::Vector { layer, modification_type } => {
|
||||
if layer == LayerNodeIdentifier::ROOT_PARENT {
|
||||
log::error!("Cannot run Vector on ROOT_PARENT");
|
||||
|
||||
@@ -4,7 +4,7 @@ use crate::messages::portfolio::document::utility_types::document_metadata::Laye
|
||||
use crate::messages::portfolio::document::utility_types::network_interface::{self, InputConnector, NodeNetworkInterface, OutputConnector};
|
||||
use crate::messages::prelude::*;
|
||||
use bezier_rs::Subpath;
|
||||
use glam::{DAffine2, DVec2, IVec2};
|
||||
use glam::{DAffine2, IVec2};
|
||||
use graph_craft::concrete;
|
||||
use graph_craft::document::value::TaggedValue;
|
||||
use graph_craft::document::{NodeId, NodeInput};
|
||||
@@ -458,12 +458,6 @@ impl<'a> ModifyInputsContext<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn pivot_set(&mut self, new_pivot: DVec2) {
|
||||
let Some(transform_node_id) = self.existing_node_id("Transform", true) else { return };
|
||||
|
||||
self.set_input_with_refresh(InputConnector::node(transform_node_id, 5), NodeInput::value(TaggedValue::DVec2(new_pivot), false), false);
|
||||
}
|
||||
|
||||
pub fn vector_modify(&mut self, modification_type: VectorModificationType) {
|
||||
let Some(path_node_id) = self.existing_node_id("Path", true) else { return };
|
||||
self.network_interface.vector_modify(&path_node_id, modification_type);
|
||||
|
||||
@@ -13,6 +13,7 @@ use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo};
|
||||
use glam::{DAffine2, DVec2};
|
||||
use graph_craft::document::NodeId;
|
||||
|
||||
#[derive(ExtractField)]
|
||||
pub struct NavigationMessageData<'a> {
|
||||
pub network_interface: &'a mut NodeNetworkInterface,
|
||||
pub breadcrumb_network_path: &'a [NodeId],
|
||||
@@ -23,7 +24,7 @@ pub struct NavigationMessageData<'a> {
|
||||
pub preferences: &'a PreferencesMessageHandler,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Default)]
|
||||
#[derive(Debug, Clone, PartialEq, Default, ExtractField)]
|
||||
pub struct NavigationMessageHandler {
|
||||
navigation_operation: NavigationOperation,
|
||||
mouse_position: ViewportPosition,
|
||||
@@ -31,6 +32,7 @@ pub struct NavigationMessageHandler {
|
||||
abortable_pan_start: Option<f64>,
|
||||
}
|
||||
|
||||
#[message_handler_data]
|
||||
impl MessageHandler<NavigationMessage, NavigationMessageData<'_>> for NavigationMessageHandler {
|
||||
fn process_message(&mut self, message: NavigationMessage, responses: &mut VecDeque<Message>, data: NavigationMessageData) {
|
||||
let NavigationMessageData {
|
||||
|
||||
@@ -21,6 +21,7 @@ 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::text::{Font, TypesettingConfig};
|
||||
#[allow(unused_imports)]
|
||||
use graphene_std::transform::Footprint;
|
||||
use graphene_std::vector::VectorDataTable;
|
||||
use graphene_std::*;
|
||||
@@ -88,7 +89,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
category: "General",
|
||||
node_template: NodeTemplate {
|
||||
document_node: DocumentNode {
|
||||
implementation: DocumentNodeImplementation::proto("graphene_core::ops::IdentityNode"),
|
||||
implementation: DocumentNodeImplementation::ProtoNode(ops::identity::IDENTIFIER),
|
||||
inputs: vec![NodeInput::value(TaggedValue::None, true)],
|
||||
..Default::default()
|
||||
},
|
||||
@@ -107,7 +108,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
category: "Debug",
|
||||
node_template: NodeTemplate {
|
||||
document_node: DocumentNode {
|
||||
implementation: DocumentNodeImplementation::proto("graphene_core::memo::MonitorNode"),
|
||||
implementation: DocumentNodeImplementation::ProtoNode(memo::monitor::IDENTIFIER),
|
||||
inputs: vec![NodeInput::value(TaggedValue::None, true)],
|
||||
manual_composition: Some(generic!(T)),
|
||||
skip_deduplication: true,
|
||||
@@ -148,19 +149,19 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
nodes: [
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::network(generic!(T), 0)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::memo::MemoNode")),
|
||||
implementation: DocumentNodeImplementation::ProtoNode(memo::memo::IDENTIFIER),
|
||||
manual_composition: Some(generic!(T)),
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::node(NodeId(0), 0)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::transform_nodes::FreezeRealTimeNode")),
|
||||
implementation: DocumentNodeImplementation::ProtoNode(transform_nodes::freeze_real_time::IDENTIFIER),
|
||||
manual_composition: Some(generic!(T)),
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::node(NodeId(1), 0)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::transform_nodes::BoundlessFootprintNode")),
|
||||
implementation: DocumentNodeImplementation::ProtoNode(transform_nodes::boundless_footprint::IDENTIFIER),
|
||||
manual_composition: Some(generic!(T)),
|
||||
..Default::default()
|
||||
},
|
||||
@@ -230,21 +231,21 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
// Secondary (left) input type coercion
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::network(generic!(T), 1)],
|
||||
implementation: DocumentNodeImplementation::proto("graphene_core::graphic_element::ToElementNode"),
|
||||
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::proto("graphene_core::graphic_element::ToGroupNode"),
|
||||
implementation: DocumentNodeImplementation::ProtoNode(graphic_element::to_group::IDENTIFIER),
|
||||
manual_composition: Some(concrete!(Context)),
|
||||
..Default::default()
|
||||
},
|
||||
// The monitor node is used to display a thumbnail in the UI
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::node(NodeId(0), 0)],
|
||||
implementation: DocumentNodeImplementation::proto("graphene_core::memo::MonitorNode"),
|
||||
implementation: DocumentNodeImplementation::ProtoNode(memo::monitor::IDENTIFIER),
|
||||
manual_composition: Some(concrete!(Context)),
|
||||
skip_deduplication: true,
|
||||
..Default::default()
|
||||
@@ -256,7 +257,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
NodeInput::node(NodeId(2), 0),
|
||||
NodeInput::Reflection(graph_craft::document::DocumentNodeMetadata::DocumentNodePath),
|
||||
],
|
||||
implementation: DocumentNodeImplementation::proto("graphene_core::graphic_element::LayerNode"),
|
||||
implementation: DocumentNodeImplementation::ProtoNode(graphic_element::layer::IDENTIFIER),
|
||||
..Default::default()
|
||||
},
|
||||
]
|
||||
@@ -337,7 +338,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
// 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::proto("graphene_core::graphic_element::ToArtboardNode"),
|
||||
implementation: DocumentNodeImplementation::ProtoNode(graphic_element::to_artboard::IDENTIFIER),
|
||||
inputs: vec![
|
||||
NodeInput::network(concrete!(TaggedValue), 1),
|
||||
NodeInput::value(TaggedValue::String(String::from("Artboard")), false),
|
||||
@@ -352,7 +353,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
// TODO: Check if thumbnail is reversed
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::node(NodeId(0), 0)],
|
||||
implementation: DocumentNodeImplementation::proto("graphene_core::memo::MonitorNode"),
|
||||
implementation: DocumentNodeImplementation::ProtoNode(memo::monitor::IDENTIFIER),
|
||||
manual_composition: Some(generic!(T)),
|
||||
skip_deduplication: true,
|
||||
..Default::default()
|
||||
@@ -364,7 +365,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
NodeInput::node(NodeId(1), 0),
|
||||
NodeInput::Reflection(graph_craft::document::DocumentNodeMetadata::DocumentNodePath),
|
||||
],
|
||||
implementation: DocumentNodeImplementation::proto("graphene_core::graphic_element::AppendArtboardNode"),
|
||||
implementation: DocumentNodeImplementation::ProtoNode(graphic_element::append_artboard::IDENTIFIER),
|
||||
..Default::default()
|
||||
},
|
||||
]
|
||||
@@ -466,13 +467,13 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::value(TaggedValue::None, false), NodeInput::scope("editor-api"), NodeInput::network(concrete!(String), 1)],
|
||||
manual_composition: Some(concrete!(Context)),
|
||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_std::wasm_application_io::LoadResourceNode")),
|
||||
implementation: DocumentNodeImplementation::ProtoNode(wasm_application_io::load_resource::IDENTIFIER),
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::node(NodeId(0), 0)],
|
||||
manual_composition: Some(concrete!(Context)),
|
||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_std::wasm_application_io::DecodeImageNode")),
|
||||
implementation: DocumentNodeImplementation::ProtoNode(wasm_application_io::decode_image::IDENTIFIER),
|
||||
..Default::default()
|
||||
},
|
||||
]
|
||||
@@ -522,6 +523,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
description: Cow::Borrowed("Loads an image from a given URL"),
|
||||
properties: None,
|
||||
},
|
||||
#[cfg(feature = "gpu")]
|
||||
DocumentNodeDefinition {
|
||||
identifier: "Create Canvas",
|
||||
category: "Debug: GPU",
|
||||
@@ -532,14 +534,14 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
nodes: [
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::scope("editor-api")],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_std::wasm_application_io::CreateSurfaceNode")),
|
||||
implementation: DocumentNodeImplementation::ProtoNode(wasm_application_io::create_surface::IDENTIFIER),
|
||||
skip_deduplication: true,
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNode {
|
||||
manual_composition: Some(concrete!(Context)),
|
||||
inputs: vec![NodeInput::node(NodeId(0), 0)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::memo::MemoNode")),
|
||||
implementation: DocumentNodeImplementation::ProtoNode(memo::memo::IDENTIFIER),
|
||||
..Default::default()
|
||||
},
|
||||
]
|
||||
@@ -587,99 +589,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
description: Cow::Borrowed("Creates a new canvas object."),
|
||||
properties: None,
|
||||
},
|
||||
DocumentNodeDefinition {
|
||||
identifier: "Draw Canvas",
|
||||
category: "Debug: GPU",
|
||||
node_template: NodeTemplate {
|
||||
document_node: DocumentNode {
|
||||
implementation: DocumentNodeImplementation::Network(NodeNetwork {
|
||||
exports: vec![NodeInput::node(NodeId(3), 0)],
|
||||
nodes: [
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::network(concrete!(RasterDataTable<CPU>), 0)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::ops::IntoNode<_, RasterDataTable<SRGBA8>>")),
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::scope("editor-api")],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_std::wasm_application_io::CreateSurfaceNode")),
|
||||
skip_deduplication: true,
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNode {
|
||||
manual_composition: Some(concrete!(Context)),
|
||||
inputs: vec![NodeInput::node(NodeId(1), 0)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::memo::MemoNode")),
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::node(NodeId(0), 0), NodeInput::node(NodeId(2), 0)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_std::wasm_application_io::DrawImageFrameNode")),
|
||||
..Default::default()
|
||||
},
|
||||
]
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(id, node)| (NodeId(id as u64), node))
|
||||
.collect(),
|
||||
..Default::default()
|
||||
}),
|
||||
inputs: vec![NodeInput::value(TaggedValue::RasterData(RasterDataTable::default()), true)],
|
||||
..Default::default()
|
||||
},
|
||||
persistent_node_metadata: DocumentNodePersistentMetadata {
|
||||
input_metadata: vec![("In", "TODO").into()],
|
||||
output_names: vec!["Canvas".to_string()],
|
||||
network_metadata: Some(NodeNetworkMetadata {
|
||||
persistent_metadata: NodeNetworkPersistentMetadata {
|
||||
node_metadata: [
|
||||
DocumentNodeMetadata {
|
||||
persistent_metadata: DocumentNodePersistentMetadata {
|
||||
display_name: "Into".to_string(),
|
||||
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, 0)),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNodeMetadata {
|
||||
persistent_metadata: DocumentNodePersistentMetadata {
|
||||
display_name: "Create Canvas".to_string(),
|
||||
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, 2)),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNodeMetadata {
|
||||
persistent_metadata: DocumentNodePersistentMetadata {
|
||||
display_name: "Cache".to_string(),
|
||||
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(7, 2)),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNodeMetadata {
|
||||
persistent_metadata: DocumentNodePersistentMetadata {
|
||||
display_name: "Draw Canvas".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("Draws raster data to a canvas element."),
|
||||
properties: None,
|
||||
},
|
||||
#[cfg(all(feature = "gpu", target_arch = "wasm32"))]
|
||||
DocumentNodeDefinition {
|
||||
identifier: "Rasterize",
|
||||
category: "Raster",
|
||||
@@ -690,20 +600,20 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
nodes: [
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::scope("editor-api")],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_std::wasm_application_io::CreateSurfaceNode")),
|
||||
implementation: DocumentNodeImplementation::ProtoNode(wasm_application_io::create_surface::IDENTIFIER),
|
||||
manual_composition: Some(concrete!(Context)),
|
||||
skip_deduplication: true,
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::node(NodeId(0), 0)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::memo::MemoNode")),
|
||||
implementation: DocumentNodeImplementation::ProtoNode(memo::memo::IDENTIFIER),
|
||||
manual_composition: Some(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(ProtoNodeIdentifier::new("graphene_std::wasm_application_io::RasterizeNode")),
|
||||
implementation: DocumentNodeImplementation::ProtoNode(wasm_application_io::rasterize::IDENTIFIER),
|
||||
manual_composition: Some(concrete!(Context)),
|
||||
..Default::default()
|
||||
},
|
||||
@@ -778,7 +688,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
node_template: NodeTemplate {
|
||||
document_node: DocumentNode {
|
||||
manual_composition: Some(concrete!(Context)),
|
||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_raster_nodes::std_nodes::NoisePatternNode")),
|
||||
implementation: DocumentNodeImplementation::ProtoNode(raster_nodes::std_nodes::noise_pattern::IDENTIFIER),
|
||||
inputs: vec![
|
||||
NodeInput::value(TaggedValue::None, false),
|
||||
NodeInput::value(TaggedValue::Bool(true), false),
|
||||
@@ -843,7 +753,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
NodeInput::network(concrete!(RasterDataTable<CPU>), 0),
|
||||
NodeInput::value(TaggedValue::RedGreenBlueAlpha(RedGreenBlueAlpha::Red), false),
|
||||
],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::raster::adjustments::ExtractChannelNode")),
|
||||
implementation: DocumentNodeImplementation::ProtoNode(raster_nodes::adjustments::extract_channel::IDENTIFIER),
|
||||
manual_composition: Some(generic!(T)),
|
||||
..Default::default()
|
||||
},
|
||||
@@ -852,7 +762,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
NodeInput::network(concrete!(RasterDataTable<CPU>), 0),
|
||||
NodeInput::value(TaggedValue::RedGreenBlueAlpha(RedGreenBlueAlpha::Green), false),
|
||||
],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::raster::adjustments::ExtractChannelNode")),
|
||||
implementation: DocumentNodeImplementation::ProtoNode(raster_nodes::adjustments::extract_channel::IDENTIFIER),
|
||||
manual_composition: Some(generic!(T)),
|
||||
..Default::default()
|
||||
},
|
||||
@@ -861,7 +771,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
NodeInput::network(concrete!(RasterDataTable<CPU>), 0),
|
||||
NodeInput::value(TaggedValue::RedGreenBlueAlpha(RedGreenBlueAlpha::Blue), false),
|
||||
],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::raster::adjustments::ExtractChannelNode")),
|
||||
implementation: DocumentNodeImplementation::ProtoNode(raster_nodes::adjustments::extract_channel::IDENTIFIER),
|
||||
manual_composition: Some(generic!(T)),
|
||||
..Default::default()
|
||||
},
|
||||
@@ -870,7 +780,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
NodeInput::network(concrete!(RasterDataTable<CPU>), 0),
|
||||
NodeInput::value(TaggedValue::RedGreenBlueAlpha(RedGreenBlueAlpha::Alpha), false),
|
||||
],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::raster::adjustments::ExtractChannelNode")),
|
||||
implementation: DocumentNodeImplementation::ProtoNode(raster_nodes::adjustments::extract_channel::IDENTIFIER),
|
||||
manual_composition: Some(generic!(T)),
|
||||
..Default::default()
|
||||
},
|
||||
@@ -948,13 +858,13 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
nodes: [
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::network(concrete!(RasterDataTable<CPU>), 0), NodeInput::value(TaggedValue::XY(XY::X), false)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::extract_xy::ExtractXyNode")),
|
||||
implementation: DocumentNodeImplementation::ProtoNode(extract_xy::extract_xy::IDENTIFIER),
|
||||
manual_composition: Some(generic!(T)),
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::network(concrete!(RasterDataTable<CPU>), 0), NodeInput::value(TaggedValue::XY(XY::Y), false)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::extract_xy::ExtractXyNode")),
|
||||
implementation: DocumentNodeImplementation::ProtoNode(extract_xy::extract_xy::IDENTIFIER),
|
||||
manual_composition: Some(generic!(T)),
|
||||
..Default::default()
|
||||
},
|
||||
@@ -1024,7 +934,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
NodeInput::network(concrete!(BrushCache), 2),
|
||||
],
|
||||
manual_composition: Some(concrete!(Context)),
|
||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_brush::brush::BrushNode")),
|
||||
implementation: DocumentNodeImplementation::ProtoNode(brush::brush::brush::IDENTIFIER),
|
||||
..Default::default()
|
||||
}]
|
||||
.into_iter()
|
||||
@@ -1036,7 +946,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
inputs: vec![
|
||||
NodeInput::value(TaggedValue::RasterData(RasterDataTable::default()), true),
|
||||
NodeInput::value(TaggedValue::BrushStrokes(Vec::new()), false),
|
||||
NodeInput::value(TaggedValue::BrushCache(BrushCache::new_proto()), false),
|
||||
NodeInput::value(TaggedValue::BrushCache(BrushCache::default()), false),
|
||||
],
|
||||
..Default::default()
|
||||
},
|
||||
@@ -1072,7 +982,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
category: "Debug",
|
||||
node_template: NodeTemplate {
|
||||
document_node: DocumentNode {
|
||||
implementation: DocumentNodeImplementation::proto("graphene_core::memo::MemoNode"),
|
||||
implementation: DocumentNodeImplementation::ProtoNode(memo::memo::IDENTIFIER),
|
||||
inputs: vec![NodeInput::value(TaggedValue::RasterData(RasterDataTable::default()), true)],
|
||||
manual_composition: Some(concrete!(Context)),
|
||||
..Default::default()
|
||||
@@ -1091,7 +1001,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
category: "Debug",
|
||||
node_template: NodeTemplate {
|
||||
document_node: DocumentNode {
|
||||
implementation: DocumentNodeImplementation::proto("graphene_core::memo::ImpureMemoNode"),
|
||||
implementation: DocumentNodeImplementation::ProtoNode(memo::impure_memo::IDENTIFIER),
|
||||
inputs: vec![NodeInput::value(TaggedValue::RasterData(RasterDataTable::default()), true)],
|
||||
manual_composition: Some(concrete!(Context)),
|
||||
..Default::default()
|
||||
@@ -1105,164 +1015,6 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
description: Cow::Borrowed("TODO"),
|
||||
properties: None,
|
||||
},
|
||||
DocumentNodeDefinition {
|
||||
identifier: "Storage",
|
||||
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")),
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::network(concrete!(Vec<u8>), 0), NodeInput::node(NodeId(0), 0)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("wgpu_executor::StorageNode")),
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNode {
|
||||
manual_composition: Some(concrete!(Context)),
|
||||
inputs: vec![NodeInput::node(NodeId(1), 0)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::memo::MemoNode")),
|
||||
..Default::default()
|
||||
},
|
||||
]
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(id, node)| (NodeId(id as u64), node))
|
||||
.collect(),
|
||||
..Default::default()
|
||||
}),
|
||||
inputs: vec![NodeInput::value(TaggedValue::None, true)],
|
||||
..Default::default()
|
||||
},
|
||||
persistent_node_metadata: DocumentNodePersistentMetadata {
|
||||
input_metadata: vec![("In", "TODO").into()],
|
||||
output_names: vec!["Storage".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: "Create Storage".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: "Create Output Buffer",
|
||||
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")),
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::network(concrete!(usize), 0), NodeInput::node(NodeId(0), 0), NodeInput::network(concrete!(Type), 1)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("wgpu_executor::CreateOutputBufferNode")),
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNode {
|
||||
manual_composition: Some(concrete!(Context)),
|
||||
inputs: vec![NodeInput::node(NodeId(1), 0)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::memo::MemoNode")),
|
||||
..Default::default()
|
||||
},
|
||||
]
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(id, node)| (NodeId(id as u64), node))
|
||||
.collect(),
|
||||
..Default::default()
|
||||
}),
|
||||
inputs: vec![NodeInput::value(TaggedValue::None, true), NodeInput::value(TaggedValue::None, true)],
|
||||
..Default::default()
|
||||
},
|
||||
persistent_node_metadata: DocumentNodePersistentMetadata {
|
||||
input_metadata: vec![("In", "TODO").into(), ("In", "TODO").into()],
|
||||
output_names: vec!["Output Buffer".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: "Create Output Buffer".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,
|
||||
},
|
||||
#[cfg(feature = "gpu")]
|
||||
DocumentNodeDefinition {
|
||||
identifier: "Create GPU Surface",
|
||||
@@ -1275,13 +1027,13 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
DocumentNode {
|
||||
manual_composition: Some(concrete!(Context)),
|
||||
inputs: vec![NodeInput::scope("editor-api")],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("wgpu_executor::CreateGpuSurfaceNode")),
|
||||
implementation: DocumentNodeImplementation::ProtoNode(wgpu_executor::create_gpu_surface::IDENTIFIER),
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNode {
|
||||
manual_composition: Some(concrete!(Context)),
|
||||
inputs: vec![NodeInput::node(NodeId(0), 0)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::memo::ImpureMemoNode")),
|
||||
implementation: DocumentNodeImplementation::ProtoNode(memo::impure_memo::IDENTIFIER),
|
||||
..Default::default()
|
||||
},
|
||||
]
|
||||
@@ -1329,87 +1081,6 @@ 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!(RasterDataTable<CPU>), 0), NodeInput::node(NodeId(0), 0)],
|
||||
manual_composition: Some(generic!(T)),
|
||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("wgpu_executor::UploadTextureNode")),
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNode {
|
||||
manual_composition: Some(generic!(T)),
|
||||
inputs: vec![NodeInput::node(NodeId(1), 0)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::memo::ImpureMemoNode")),
|
||||
..Default::default()
|
||||
},
|
||||
]
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(id, node)| (NodeId(id as u64), node))
|
||||
.collect(),
|
||||
..Default::default()
|
||||
}),
|
||||
inputs: vec![NodeInput::value(TaggedValue::RasterData(RasterDataTable::default()), true)],
|
||||
..Default::default()
|
||||
},
|
||||
persistent_node_metadata: DocumentNodePersistentMetadata {
|
||||
input_metadata: vec![("In", "TODO").into()],
|
||||
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",
|
||||
@@ -1466,15 +1137,19 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
nodes: vec![
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::network(concrete!(VectorDataTable), 0)],
|
||||
implementation: DocumentNodeImplementation::proto("graphene_core::memo::MonitorNode"),
|
||||
implementation: DocumentNodeImplementation::ProtoNode(memo::monitor::IDENTIFIER),
|
||||
manual_composition: Some(generic!(T)),
|
||||
skip_deduplication: true,
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::node(NodeId(0), 0), NodeInput::network(concrete!(graphene_std::vector::VectorModification), 1)],
|
||||
inputs: vec![
|
||||
NodeInput::node(NodeId(0), 0),
|
||||
NodeInput::network(concrete!(graphene_std::vector::VectorModification), 1),
|
||||
NodeInput::Reflection(graph_craft::document::DocumentNodeMetadata::DocumentNodePath),
|
||||
],
|
||||
manual_composition: Some(generic!(T)),
|
||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::vector::vector_data::modification::PathModifyNode")),
|
||||
implementation: DocumentNodeImplementation::ProtoNode(vector::path_modify::IDENTIFIER),
|
||||
..Default::default()
|
||||
},
|
||||
]
|
||||
@@ -1532,7 +1207,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
category: "Text",
|
||||
node_template: NodeTemplate {
|
||||
document_node: DocumentNode {
|
||||
implementation: DocumentNodeImplementation::proto("graphene_std::text::TextNode"),
|
||||
implementation: DocumentNodeImplementation::ProtoNode(text::text::IDENTIFIER),
|
||||
manual_composition: Some(concrete!(Context)),
|
||||
inputs: vec![
|
||||
NodeInput::scope("editor-api"),
|
||||
@@ -1633,14 +1308,13 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
NodeInput::value(TaggedValue::F64(0.), false),
|
||||
NodeInput::value(TaggedValue::DVec2(DVec2::ONE), false),
|
||||
NodeInput::value(TaggedValue::DVec2(DVec2::ZERO), false),
|
||||
NodeInput::value(TaggedValue::DVec2(DVec2::splat(0.5)), false),
|
||||
],
|
||||
implementation: DocumentNodeImplementation::Network(NodeNetwork {
|
||||
exports: vec![NodeInput::node(NodeId(1), 0)],
|
||||
nodes: [
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::network(concrete!(VectorDataTable), 0)],
|
||||
implementation: DocumentNodeImplementation::proto("graphene_core::memo::MonitorNode"),
|
||||
implementation: DocumentNodeImplementation::ProtoNode(memo::monitor::IDENTIFIER),
|
||||
manual_composition: Some(generic!(T)),
|
||||
skip_deduplication: true,
|
||||
..Default::default()
|
||||
@@ -1652,10 +1326,9 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
NodeInput::network(concrete!(f64), 2),
|
||||
NodeInput::network(concrete!(DVec2), 3),
|
||||
NodeInput::network(concrete!(DVec2), 4),
|
||||
NodeInput::network(concrete!(DVec2), 5),
|
||||
],
|
||||
manual_composition: Some(concrete!(Context)),
|
||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::transform_nodes::TransformNode")),
|
||||
implementation: DocumentNodeImplementation::ProtoNode(transform_nodes::transform::IDENTIFIER),
|
||||
..Default::default()
|
||||
},
|
||||
]
|
||||
@@ -1720,7 +1393,6 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
}),
|
||||
),
|
||||
InputMetadata::with_name_description_override("Skew", "TODO", WidgetOverride::Custom("transform_skew".to_string())),
|
||||
InputMetadata::with_name_description_override("Pivot", "TODO", WidgetOverride::Hidden),
|
||||
],
|
||||
output_names: vec!["Data".to_string()],
|
||||
..Default::default()
|
||||
@@ -1739,25 +1411,25 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
nodes: vec![
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::network(concrete!(VectorDataTable), 0), NodeInput::network(concrete!(vector::style::Fill), 1)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_path_bool::BooleanOperationNode")),
|
||||
implementation: DocumentNodeImplementation::ProtoNode(path_bool::boolean_operation::IDENTIFIER),
|
||||
manual_composition: Some(generic!(T)),
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::node(NodeId(0), 0)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::memo::MemoNode")),
|
||||
implementation: DocumentNodeImplementation::ProtoNode(memo::memo::IDENTIFIER),
|
||||
manual_composition: Some(generic!(T)),
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::node(NodeId(1), 0)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::transform_nodes::FreezeRealTimeNode")),
|
||||
implementation: DocumentNodeImplementation::ProtoNode(transform_nodes::freeze_real_time::IDENTIFIER),
|
||||
manual_composition: Some(generic!(T)),
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::node(NodeId(2), 0)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::transform_nodes::BoundlessFootprintNode")),
|
||||
implementation: DocumentNodeImplementation::ProtoNode(transform_nodes::boundless_footprint::IDENTIFIER),
|
||||
manual_composition: Some(generic!(T)),
|
||||
..Default::default()
|
||||
},
|
||||
@@ -1837,7 +1509,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
nodes: [
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::network(concrete!(graphene_std::vector::VectorDataTable), 0)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::vector::SubpathSegmentLengthsNode")),
|
||||
implementation: DocumentNodeImplementation::ProtoNode(vector::subpath_segment_lengths::IDENTIFIER),
|
||||
manual_composition: Some(generic!(T)),
|
||||
..Default::default()
|
||||
},
|
||||
@@ -1852,25 +1524,25 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
NodeInput::network(concrete!(bool), 6),
|
||||
NodeInput::node(NodeId(0), 0),
|
||||
],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::vector::SamplePolylineNode")),
|
||||
implementation: DocumentNodeImplementation::ProtoNode(vector::sample_polyline::IDENTIFIER),
|
||||
manual_composition: Some(generic!(T)),
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::node(NodeId(1), 0)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::memo::MemoNode")),
|
||||
implementation: DocumentNodeImplementation::ProtoNode(memo::memo::IDENTIFIER),
|
||||
manual_composition: Some(generic!(T)),
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::node(NodeId(2), 0)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::transform_nodes::FreezeRealTimeNode")),
|
||||
implementation: DocumentNodeImplementation::ProtoNode(transform_nodes::freeze_real_time::IDENTIFIER),
|
||||
manual_composition: Some(generic!(T)),
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::node(NodeId(3), 0)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::transform_nodes::BoundlessFootprintNode")),
|
||||
implementation: DocumentNodeImplementation::ProtoNode(transform_nodes::boundless_footprint::IDENTIFIER),
|
||||
manual_composition: Some(generic!(T)),
|
||||
..Default::default()
|
||||
},
|
||||
@@ -2008,24 +1680,24 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
NodeInput::network(concrete!(u32), 2),
|
||||
],
|
||||
manual_composition: Some(generic!(T)),
|
||||
implementation: DocumentNodeImplementation::proto("graphene_core::vector::PoissonDiskPointsNode"),
|
||||
implementation: DocumentNodeImplementation::ProtoNode(vector::poisson_disk_points::IDENTIFIER),
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::node(NodeId(0), 0)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::memo::MemoNode")),
|
||||
implementation: DocumentNodeImplementation::ProtoNode(memo::memo::IDENTIFIER),
|
||||
manual_composition: Some(generic!(T)),
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::node(NodeId(1), 0)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::transform_nodes::FreezeRealTimeNode")),
|
||||
implementation: DocumentNodeImplementation::ProtoNode(transform_nodes::freeze_real_time::IDENTIFIER),
|
||||
manual_composition: Some(generic!(T)),
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::node(NodeId(2), 0)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::transform_nodes::BoundlessFootprintNode")),
|
||||
implementation: DocumentNodeImplementation::ProtoNode(transform_nodes::boundless_footprint::IDENTIFIER),
|
||||
manual_composition: Some(generic!(T)),
|
||||
..Default::default()
|
||||
},
|
||||
@@ -2645,11 +2317,11 @@ pub fn resolve_document_node_type(identifier: &str) -> Option<&DocumentNodeDefin
|
||||
|
||||
pub fn collect_node_types() -> Vec<FrontendNodeType> {
|
||||
// Create a mapping from registry ID to document node identifier
|
||||
let id_to_identifier_map: HashMap<String, &'static str> = DOCUMENT_NODE_TYPES
|
||||
let id_to_identifier_map: HashMap<ProtoNodeIdentifier, &'static str> = DOCUMENT_NODE_TYPES
|
||||
.iter()
|
||||
.filter_map(|definition| {
|
||||
if let DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier { name }) = &definition.node_template.document_node.implementation {
|
||||
Some((name.to_string(), definition.identifier))
|
||||
if let DocumentNodeImplementation::ProtoNode(name) = &definition.node_template.document_node.implementation {
|
||||
Some((name.clone(), definition.identifier))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
@@ -2657,28 +2329,28 @@ pub fn collect_node_types() -> Vec<FrontendNodeType> {
|
||||
.collect();
|
||||
let mut extracted_node_types = Vec::new();
|
||||
|
||||
let node_registry = graphene_std::registry::NODE_REGISTRY.lock().unwrap();
|
||||
let node_metadata = graphene_std::registry::NODE_METADATA.lock().unwrap();
|
||||
let node_registry = registry::NODE_REGISTRY.lock().unwrap();
|
||||
let node_metadata = registry::NODE_METADATA.lock().unwrap();
|
||||
for (id, metadata) in node_metadata.iter() {
|
||||
if let Some(implementations) = node_registry.get(id) {
|
||||
let identifier = match id_to_identifier_map.get(id) {
|
||||
Some(&id) => id.to_string(),
|
||||
Some(&id) => id,
|
||||
None => continue,
|
||||
};
|
||||
|
||||
// Extract category from metadata (already creates an owned String)
|
||||
let category = metadata.category.unwrap_or_default().to_string();
|
||||
let category = metadata.category.unwrap_or_default();
|
||||
|
||||
// Extract input types (already creates owned Strings)
|
||||
let input_types = implementations
|
||||
.iter()
|
||||
.flat_map(|(_, node_io)| node_io.inputs.iter().map(|ty| ty.nested_type().to_string()))
|
||||
.collect::<HashSet<String>>()
|
||||
.flat_map(|(_, node_io)| node_io.inputs.iter().map(|ty| ty.nested_type().to_cow_string()))
|
||||
.collect::<HashSet<Cow<'static, str>>>()
|
||||
.into_iter()
|
||||
.collect::<Vec<String>>();
|
||||
.collect::<Vec<Cow<'static, str>>>();
|
||||
|
||||
// Create a FrontendNodeType
|
||||
let node_type = FrontendNodeType::with_owned_strings_and_input_types(identifier, category, input_types);
|
||||
let node_type = FrontendNodeType::with_input_types(identifier, category, input_types);
|
||||
|
||||
// Store the created node_type
|
||||
extracted_node_types.push(node_type);
|
||||
@@ -2694,8 +2366,8 @@ pub fn collect_node_types() -> Vec<FrontendNodeType> {
|
||||
.document_node
|
||||
.inputs
|
||||
.iter()
|
||||
.filter_map(|node_input| node_input.as_value().map(|node_value| node_value.ty().nested_type().to_string()))
|
||||
.collect::<Vec<String>>();
|
||||
.filter_map(|node_input| node_input.as_value().map(|node_value| node_value.ty().nested_type().to_cow_string()))
|
||||
.collect::<Vec<Cow<'static, str>>>();
|
||||
|
||||
FrontendNodeType::with_input_types(definition.identifier, definition.category, input_types)
|
||||
})
|
||||
|
||||
+2
-2
@@ -21,7 +21,7 @@ pub(super) fn post_process_nodes(mut custom: Vec<DocumentNodeDefinition>) -> Vec
|
||||
};
|
||||
}
|
||||
|
||||
let node_registry = graphene_core::registry::NODE_REGISTRY.lock().unwrap();
|
||||
let node_registry = NODE_REGISTRY.lock().unwrap();
|
||||
'outer: for (id, metadata) in NODE_METADATA.lock().unwrap().iter() {
|
||||
for node in custom.iter() {
|
||||
let DocumentNodeDefinition {
|
||||
@@ -32,7 +32,7 @@ pub(super) fn post_process_nodes(mut custom: Vec<DocumentNodeDefinition>) -> Vec
|
||||
..
|
||||
} = node;
|
||||
match implementation {
|
||||
DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier { name }) if name == id => continue 'outer,
|
||||
DocumentNodeImplementation::ProtoNode(name) if name == id => continue 'outer,
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ use graphene_std::*;
|
||||
use renderer::Quad;
|
||||
use std::cmp::Ordering;
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, ExtractField)]
|
||||
pub struct NodeGraphHandlerData<'a> {
|
||||
pub network_interface: &'a mut NodeNetworkInterface,
|
||||
pub selection_network_path: &'a [NodeId],
|
||||
@@ -41,7 +41,7 @@ pub struct NodeGraphHandlerData<'a> {
|
||||
pub preferences: &'a PreferencesMessageHandler,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, ExtractField)]
|
||||
pub struct NodeGraphMessageHandler {
|
||||
// TODO: Remove network and move to NodeNetworkInterface
|
||||
pub network: Vec<NodeId>,
|
||||
@@ -92,6 +92,7 @@ pub struct NodeGraphMessageHandler {
|
||||
}
|
||||
|
||||
/// NodeGraphMessageHandler always modifies the network which the selected nodes are in. No GraphOperationMessages should be added here, since those messages will always affect the document network.
|
||||
#[message_handler_data]
|
||||
impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGraphMessageHandler {
|
||||
fn process_message(&mut self, message: NodeGraphMessage, responses: &mut VecDeque<Message>, data: NodeGraphHandlerData<'a>) {
|
||||
let NodeGraphHandlerData {
|
||||
|
||||
@@ -1274,6 +1274,7 @@ pub(crate) fn rectangle_properties(node_id: NodeId, context: &mut NodeProperties
|
||||
|
||||
let mut corner_radius_row_2 = vec![Separator::new(SeparatorType::Unrelated).widget_holder()];
|
||||
corner_radius_row_2.push(TextLabel::new("").widget_holder());
|
||||
add_blank_assist(&mut corner_radius_row_2);
|
||||
|
||||
let document_node = match get_document_node(node_id, context) {
|
||||
Ok(document_node) => document_node,
|
||||
@@ -1381,8 +1382,6 @@ pub(crate) fn rectangle_properties(node_id: NodeId, context: &mut NodeProperties
|
||||
// Size Y
|
||||
let size_y = number_widget(ParameterWidgetsInfo::new(node_id, HeightInput::INDEX, true, context), NumberInput::default());
|
||||
|
||||
add_blank_assist(&mut corner_radius_row_2);
|
||||
|
||||
// Clamped
|
||||
let clamped = bool_widget(ParameterWidgetsInfo::new(node_id, ClampedInput::INDEX, true, context), CheckboxInput::default());
|
||||
|
||||
@@ -1436,7 +1435,7 @@ pub(crate) fn generate_node_properties(node_id: NodeId, context: &mut NodeProper
|
||||
if let Some(field) = graphene_std::registry::NODE_METADATA
|
||||
.lock()
|
||||
.unwrap()
|
||||
.get(&proto_node_identifier.name.clone().into_owned())
|
||||
.get(proto_node_identifier)
|
||||
.and_then(|metadata| metadata.fields.get(input_index))
|
||||
{
|
||||
number_options = (field.number_min, field.number_max, field.number_mode_range);
|
||||
|
||||
@@ -2,6 +2,7 @@ use crate::messages::portfolio::document::utility_types::network_interface::{Inp
|
||||
use graph_craft::document::NodeId;
|
||||
use graph_craft::document::value::TaggedValue;
|
||||
use graphene_std::Type;
|
||||
use std::borrow::Cow;
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Hash, serde::Serialize, serde::Deserialize, specta::Type)]
|
||||
pub enum FrontendGraphDataType {
|
||||
@@ -98,33 +99,25 @@ pub struct FrontendNode {
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
|
||||
pub struct FrontendNodeType {
|
||||
pub name: String,
|
||||
pub category: String,
|
||||
pub name: Cow<'static, str>,
|
||||
pub category: Cow<'static, str>,
|
||||
#[serde(rename = "inputTypes")]
|
||||
pub input_types: Option<Vec<String>>,
|
||||
pub input_types: Option<Vec<Cow<'static, str>>>,
|
||||
}
|
||||
|
||||
impl FrontendNodeType {
|
||||
pub fn new(name: &'static str, category: &'static str) -> Self {
|
||||
pub fn new(name: impl Into<Cow<'static, str>>, category: impl Into<Cow<'static, str>>) -> Self {
|
||||
Self {
|
||||
name: name.to_string(),
|
||||
category: category.to_string(),
|
||||
name: name.into(),
|
||||
category: category.into(),
|
||||
input_types: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_input_types(name: &'static str, category: &'static str, input_types: Vec<String>) -> Self {
|
||||
pub fn with_input_types(name: impl Into<Cow<'static, str>>, category: impl Into<Cow<'static, str>>, input_types: Vec<Cow<'static, str>>) -> Self {
|
||||
Self {
|
||||
name: name.to_string(),
|
||||
category: category.to_string(),
|
||||
input_types: Some(input_types),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_owned_strings_and_input_types(name: String, category: String, input_types: Vec<String>) -> Self {
|
||||
Self {
|
||||
name,
|
||||
category,
|
||||
name: name.into(),
|
||||
category: category.into(),
|
||||
input_types: Some(input_types),
|
||||
}
|
||||
}
|
||||
@@ -198,7 +191,7 @@ pub struct FrontendClickTargets {
|
||||
pub modify_import_export: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
|
||||
pub enum Direction {
|
||||
Up,
|
||||
Down,
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
use super::utility_types::{OverlayProvider, OverlaysVisibilitySettings};
|
||||
use crate::messages::prelude::*;
|
||||
|
||||
#[derive(ExtractField)]
|
||||
pub struct OverlaysMessageData<'a> {
|
||||
pub visibility_settings: OverlaysVisibilitySettings,
|
||||
pub ipp: &'a InputPreprocessorMessageHandler,
|
||||
pub device_pixel_ratio: f64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
#[derive(Debug, Clone, Default, ExtractField)]
|
||||
pub struct OverlaysMessageHandler {
|
||||
pub overlay_providers: HashSet<OverlayProvider>,
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
@@ -16,6 +17,7 @@ pub struct OverlaysMessageHandler {
|
||||
context: Option<web_sys::CanvasRenderingContext2d>,
|
||||
}
|
||||
|
||||
#[message_handler_data]
|
||||
impl MessageHandler<OverlaysMessage, OverlaysMessageData<'_>> for OverlaysMessageHandler {
|
||||
fn process_message(&mut self, message: OverlaysMessage, responses: &mut VecDeque<Message>, data: OverlaysMessageData) {
|
||||
let OverlaysMessageData { visibility_settings, ipp, .. } = data;
|
||||
|
||||
@@ -119,7 +119,7 @@ pub fn path_overlays(document: &DocumentMessageHandler, draw_handles: DrawHandle
|
||||
|
||||
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 transform = document.metadata().transform_to_viewport(layer);
|
||||
let transform = document.metadata().transform_to_viewport_if_feeds(layer, &document.network_interface);
|
||||
if display_path {
|
||||
overlay_context.outline_vector(&vector_data, transform);
|
||||
}
|
||||
@@ -196,7 +196,7 @@ pub fn path_endpoint_overlays(document: &DocumentMessageHandler, shape_editor: &
|
||||
continue;
|
||||
};
|
||||
//let document_to_viewport = document.navigation_handler.calculate_offset_transform(overlay_context.size / 2., &document.document_ptz);
|
||||
let transform = document.metadata().transform_to_viewport(layer);
|
||||
let transform = document.metadata().transform_to_viewport_if_feeds(layer, &document.network_interface);
|
||||
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));
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
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, COMPASS_ROSE_ARROW_SIZE, COMPASS_ROSE_HOVER_RING_DIAMETER,
|
||||
COMPASS_ROSE_MAIN_RING_DIAMETER, COMPASS_ROSE_RING_INNER_DIAMETER, MANIPULATOR_GROUP_MARKER_SIZE, PIVOT_CROSSHAIR_LENGTH, PIVOT_CROSSHAIR_THICKNESS, PIVOT_DIAMETER,
|
||||
COLOR_OVERLAY_BLUE, 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,
|
||||
};
|
||||
use crate::messages::prelude::Message;
|
||||
use bezier_rs::{Bezier, Subpath};
|
||||
use core::borrow::Borrow;
|
||||
use core::f64::consts::{FRAC_PI_2, TAU};
|
||||
use core::f64::consts::{FRAC_PI_2, PI, TAU};
|
||||
use glam::{DAffine2, DVec2};
|
||||
use graphene_std::Color;
|
||||
use graphene_std::math::quad::Quad;
|
||||
@@ -33,12 +33,14 @@ pub enum OverlaysType {
|
||||
HoverOutline,
|
||||
SelectionOutline,
|
||||
Pivot,
|
||||
Origin,
|
||||
Path,
|
||||
Anchors,
|
||||
Handles,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Copy, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)]
|
||||
#[serde(default)]
|
||||
pub struct OverlaysVisibilitySettings {
|
||||
pub all: bool,
|
||||
pub artboard_name: bool,
|
||||
@@ -49,6 +51,7 @@ pub struct OverlaysVisibilitySettings {
|
||||
pub hover_outline: bool,
|
||||
pub selection_outline: bool,
|
||||
pub pivot: bool,
|
||||
pub origin: bool,
|
||||
pub path: bool,
|
||||
pub anchors: bool,
|
||||
pub handles: bool,
|
||||
@@ -66,6 +69,7 @@ impl Default for OverlaysVisibilitySettings {
|
||||
hover_outline: true,
|
||||
selection_outline: true,
|
||||
pivot: true,
|
||||
origin: true,
|
||||
path: true,
|
||||
anchors: true,
|
||||
handles: true,
|
||||
@@ -110,6 +114,10 @@ impl OverlaysVisibilitySettings {
|
||||
self.all && self.pivot
|
||||
}
|
||||
|
||||
pub fn origin(&self) -> bool {
|
||||
self.all && self.origin
|
||||
}
|
||||
|
||||
pub fn path(&self) -> bool {
|
||||
self.all && self.path
|
||||
}
|
||||
@@ -423,10 +431,7 @@ impl OverlayContext {
|
||||
|
||||
pub fn draw_scale(&mut self, start: DVec2, scale: f64, radius: f64, text: &str) {
|
||||
let sign = scale.signum();
|
||||
let mut fill_color = graphene_std::Color::from_rgb_str(crate::consts::COLOR_OVERLAY_WHITE.strip_prefix('#').unwrap())
|
||||
.unwrap()
|
||||
.with_alpha(0.05)
|
||||
.to_rgba_hex_srgb();
|
||||
let mut fill_color = Color::from_rgb_str(COLOR_OVERLAY_WHITE.strip_prefix('#').unwrap()).unwrap().with_alpha(0.05).to_rgba_hex_srgb();
|
||||
fill_color.insert(0, '#');
|
||||
let fill_color = Some(fill_color.as_str());
|
||||
self.line(start + DVec2::X * radius * sign, start + DVec2::X * (radius * scale), None, None);
|
||||
@@ -463,10 +468,7 @@ impl OverlayContext {
|
||||
|
||||
// Hover ring
|
||||
if show_hover_ring {
|
||||
let mut fill_color = graphene_std::Color::from_rgb_str(COLOR_OVERLAY_BLUE.strip_prefix('#').unwrap())
|
||||
.unwrap()
|
||||
.with_alpha(0.5)
|
||||
.to_rgba_hex_srgb();
|
||||
let mut fill_color = Color::from_rgb_str(COLOR_OVERLAY_BLUE.strip_prefix('#').unwrap()).unwrap().with_alpha(0.5).to_rgba_hex_srgb();
|
||||
fill_color.insert(0, '#');
|
||||
|
||||
self.render_context.set_line_width(HOVER_RING_STROKE_WIDTH);
|
||||
@@ -550,6 +552,36 @@ impl OverlayContext {
|
||||
self.end_dpi_aware_transform();
|
||||
}
|
||||
|
||||
pub fn dowel_pin(&mut self, position: DVec2, angle: f64, color: Option<&str>) {
|
||||
let (x, y) = (position.round() - DVec2::splat(0.5)).into();
|
||||
let color = color.unwrap_or(COLOR_OVERLAY_YELLOW_DULL);
|
||||
|
||||
self.start_dpi_aware_transform();
|
||||
|
||||
// Draw the background circle with a white fill and blue outline
|
||||
self.render_context.begin_path();
|
||||
self.render_context.arc(x, y, DOWEL_PIN_RADIUS, 0., TAU).expect("Failed to draw the circle");
|
||||
self.render_context.set_fill_style_str(COLOR_OVERLAY_WHITE);
|
||||
self.render_context.fill();
|
||||
self.render_context.set_stroke_style_str(color);
|
||||
self.render_context.stroke();
|
||||
|
||||
// Draw the two blue filled sectors
|
||||
self.render_context.begin_path();
|
||||
// Top-left sector
|
||||
self.render_context.move_to(x, y);
|
||||
self.render_context.arc(x, y, DOWEL_PIN_RADIUS, FRAC_PI_2 + angle, PI + angle).expect("Failed to draw arc");
|
||||
self.render_context.close_path();
|
||||
// Bottom-right sector
|
||||
self.render_context.move_to(x, y);
|
||||
self.render_context.arc(x, y, DOWEL_PIN_RADIUS, PI + FRAC_PI_2 + angle, TAU + angle).expect("Failed to draw arc");
|
||||
self.render_context.close_path();
|
||||
self.render_context.set_fill_style_str(color);
|
||||
self.render_context.fill();
|
||||
|
||||
self.end_dpi_aware_transform();
|
||||
}
|
||||
|
||||
/// 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) {
|
||||
self.start_dpi_aware_transform();
|
||||
@@ -599,9 +631,11 @@ impl OverlayContext {
|
||||
pub fn outline_overlay_bezier(&mut self, bezier: Bezier, transform: DAffine2) {
|
||||
self.start_dpi_aware_transform();
|
||||
|
||||
let color = Color::from_rgb_str(COLOR_OVERLAY_BLUE.strip_prefix('#').unwrap()).unwrap().with_alpha(0.05).to_rgba_hex_srgb();
|
||||
|
||||
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_stroke_style_str(&color);
|
||||
self.render_context.set_line_width(4.);
|
||||
self.render_context.stroke();
|
||||
|
||||
@@ -731,11 +765,11 @@ impl OverlayContext {
|
||||
// └──┴──┴──┴──┘
|
||||
let pixels = [(0, 0), (2, 2)];
|
||||
for &(x, y) in &pixels {
|
||||
let index = (x + y * PATTERN_WIDTH as usize) * 4;
|
||||
let index = (x + y * PATTERN_WIDTH) * 4;
|
||||
data[index..index + 4].copy_from_slice(&color.to_rgba8_srgb());
|
||||
}
|
||||
|
||||
let image_data = web_sys::ImageData::new_with_u8_clamped_array_and_sh(wasm_bindgen::Clamped(&mut data), PATTERN_WIDTH as u32, PATTERN_HEIGHT as u32).unwrap();
|
||||
let image_data = web_sys::ImageData::new_with_u8_clamped_array_and_sh(wasm_bindgen::Clamped(&data), PATTERN_WIDTH as u32, PATTERN_HEIGHT as u32).unwrap();
|
||||
pattern_context.put_image_data(&image_data, 0., 0.).unwrap();
|
||||
let pattern = self.render_context.create_pattern_with_offscreen_canvas(&pattern_canvas, "repeat").unwrap().unwrap();
|
||||
|
||||
@@ -780,6 +814,36 @@ impl OverlayContext {
|
||||
self.render_context.fill_text(text, 0., 0.).expect("Failed to draw the text at the calculated position");
|
||||
self.render_context.reset_transform().expect("Failed to reset the render context transform");
|
||||
}
|
||||
|
||||
pub fn translation_box(&mut self, translation: DVec2, quad: Quad, typed_string: Option<String>) {
|
||||
if translation.x.abs() > 1e-3 {
|
||||
self.dashed_line(quad.top_left(), quad.top_right(), None, None, Some(2.), Some(2.), Some(0.5));
|
||||
|
||||
let width = match typed_string {
|
||||
Some(ref typed_string) => typed_string,
|
||||
None => &format!("{:.2}", translation.x).trim_end_matches('0').trim_end_matches('.').to_string(),
|
||||
};
|
||||
let x_transform = DAffine2::from_translation((quad.top_left() + quad.top_right()) / 2.);
|
||||
self.text(width, COLOR_OVERLAY_BLUE, None, x_transform, 4., [Pivot::Middle, Pivot::End]);
|
||||
}
|
||||
|
||||
if translation.y.abs() > 1e-3 {
|
||||
self.dashed_line(quad.top_left(), quad.bottom_left(), None, None, Some(2.), Some(2.), Some(0.5));
|
||||
|
||||
let height = match typed_string {
|
||||
Some(ref typed_string) => typed_string,
|
||||
None => &format!("{:.2}", translation.y).trim_end_matches('0').trim_end_matches('.').to_string(),
|
||||
};
|
||||
let y_transform = DAffine2::from_translation((quad.top_left() + quad.bottom_left()) / 2.);
|
||||
let height_pivot = if translation.x > -1e-3 { Pivot::Start } else { Pivot::End };
|
||||
self.text(height, COLOR_OVERLAY_BLUE, None, y_transform, 3., [height_pivot, Pivot::Middle]);
|
||||
}
|
||||
|
||||
if translation.x.abs() > 1e-3 && translation.y.abs() > 1e-3 {
|
||||
self.line(quad.top_right(), quad.bottom_right(), None, None);
|
||||
self.line(quad.bottom_left(), quad.bottom_right(), None, None);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum Pivot {
|
||||
|
||||
+2
-1
@@ -4,9 +4,10 @@ use crate::messages::portfolio::document::node_graph::document_node_definitions:
|
||||
use crate::messages::portfolio::utility_types::PersistentData;
|
||||
use crate::messages::prelude::*;
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
#[derive(Debug, Clone, Default, ExtractField)]
|
||||
pub struct PropertiesPanelMessageHandler {}
|
||||
|
||||
#[message_handler_data]
|
||||
impl MessageHandler<PropertiesPanelMessage, (&PersistentData, PropertiesPanelMessageHandlerData<'_>)> for PropertiesPanelMessageHandler {
|
||||
fn process_message(&mut self, message: PropertiesPanelMessage, responses: &mut VecDeque<Message>, (persistent_data, data): (&PersistentData, PropertiesPanelMessageHandlerData)) {
|
||||
let PropertiesPanelMessageHandlerData {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
use super::network_interface::NodeNetworkInterface;
|
||||
use crate::messages::portfolio::document::graph_operation::transform_utils;
|
||||
use crate::messages::portfolio::document::graph_operation::utility_types::ModifyInputsContext;
|
||||
use crate::messages::portfolio::document::utility_types::network_interface::FlowType;
|
||||
use crate::messages::tool::common_functionality::graph_modification_utils;
|
||||
use glam::{DAffine2, DVec2};
|
||||
use graph_craft::document::NodeId;
|
||||
use graphene_std::math::quad::Quad;
|
||||
@@ -16,10 +18,11 @@ use std::num::NonZeroU64;
|
||||
|
||||
// TODO: To avoid storing a stateful snapshot of some other system's state (which is easily to accidentally get out of sync),
|
||||
// TODO: it might be better to have a system that can query the state of the node network on demand.
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, Default)]
|
||||
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 structure: HashMap<LayerNodeIdentifier, NodeRelations>,
|
||||
pub click_targets: HashMap<LayerNodeIdentifier, Vec<ClickTarget>>,
|
||||
pub clip_targets: HashSet<NodeId>,
|
||||
@@ -28,20 +31,6 @@ pub struct DocumentMetadata {
|
||||
pub document_to_viewport: DAffine2,
|
||||
}
|
||||
|
||||
impl Default for DocumentMetadata {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
upstream_footprints: HashMap::new(),
|
||||
local_transforms: HashMap::new(),
|
||||
structure: HashMap::new(),
|
||||
vector_modify: HashMap::new(),
|
||||
click_targets: HashMap::new(),
|
||||
clip_targets: HashSet::new(),
|
||||
document_to_viewport: DAffine2::IDENTITY,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// =================================
|
||||
// DocumentMetadata: Layer iterators
|
||||
// =================================
|
||||
@@ -91,6 +80,36 @@ impl DocumentMetadata {
|
||||
footprint * local_transform
|
||||
}
|
||||
|
||||
pub fn transform_to_viewport_if_feeds(&self, layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> DAffine2 {
|
||||
// We're not allowed to convert the root parent to a node id
|
||||
if layer == LayerNodeIdentifier::ROOT_PARENT {
|
||||
return self.document_to_viewport;
|
||||
}
|
||||
|
||||
let footprint = self.upstream_footprints.get(&layer.to_node()).map(|footprint| footprint.transform).unwrap_or(self.document_to_viewport);
|
||||
|
||||
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 !network_interface
|
||||
.upstream_flow_back_from_nodes(vec![path_node], &[], FlowType::HorizontalFlow)
|
||||
.any(|upstream| Some(upstream) == source)
|
||||
{
|
||||
use_local = false;
|
||||
info!("Local transform is invalid — using the identity for the local transform instead")
|
||||
}
|
||||
}
|
||||
}
|
||||
let local_transform = use_local.then(|| self.local_transforms.get(&layer.to_node()).copied()).flatten().unwrap_or_default();
|
||||
|
||||
footprint * local_transform
|
||||
}
|
||||
|
||||
pub fn transform_to_document_if_feeds(&self, layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> DAffine2 {
|
||||
self.document_to_viewport.inverse() * self.transform_to_viewport_if_feeds(layer, network_interface)
|
||||
}
|
||||
|
||||
pub fn transform_to_viewport_with_first_transform_node_if_group(&self, layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> DAffine2 {
|
||||
let footprint = self.upstream_footprints.get(&layer.to_node()).map(|footprint| footprint.transform).unwrap_or(self.document_to_viewport);
|
||||
let local_transform = self.local_transforms.get(&layer.to_node()).copied();
|
||||
|
||||
@@ -60,6 +60,27 @@ impl PartialEq for NodeNetworkInterface {
|
||||
}
|
||||
}
|
||||
|
||||
impl NodeNetworkInterface {
|
||||
/// Add DocumentNodePath input to the PathModifyNode protonode
|
||||
pub fn migrate_path_modify_node(&mut self) {
|
||||
fix_network(&mut self.network);
|
||||
fn fix_network(network: &mut NodeNetwork) {
|
||||
for node in network.nodes.values_mut() {
|
||||
if let Some(network) = node.implementation.get_network_mut() {
|
||||
fix_network(network);
|
||||
}
|
||||
if let DocumentNodeImplementation::ProtoNode(protonode) = &node.implementation {
|
||||
if protonode.name.contains("PathModifyNode") {
|
||||
if node.inputs.len() < 3 {
|
||||
node.inputs.push(NodeInput::Reflection(graph_craft::document::DocumentNodeMetadata::DocumentNodePath));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Public immutable getters for the network interface
|
||||
impl NodeNetworkInterface {
|
||||
// TODO: Make private and use .field_name getter methods
|
||||
@@ -653,7 +674,7 @@ impl NodeNetworkInterface {
|
||||
let input_type = self.input_type(&InputConnector::node(*node_id, iterator_index), network_path).0;
|
||||
// Value inputs are stored as concrete, so they are compared to the nested type. Node inputs are stored as fn, so they are compared to the entire type.
|
||||
// For example a node input of (Footprint) -> VectorData would not be compatible with () -> VectorData
|
||||
node_io.inputs[iterator_index].clone().nested_type() == &input_type || node_io.inputs[iterator_index] == input_type
|
||||
node_io.inputs.get(iterator_index).map(|ty| ty.nested_type().clone()).as_ref() == Some(&input_type) || node_io.inputs.get(iterator_index) == Some(&input_type)
|
||||
});
|
||||
if valid_implementation { node_io.inputs.get(*input_index).cloned() } else { None }
|
||||
})
|
||||
@@ -3513,6 +3534,11 @@ impl NodeNetworkInterface {
|
||||
self.document_metadata.local_transforms = local_transforms;
|
||||
}
|
||||
|
||||
/// Update the cached first instance source id of the layers
|
||||
pub fn update_first_instance_source_id(&mut self, new: HashMap<NodeId, Option<NodeId>>) {
|
||||
self.document_metadata.first_instance_source_ids = new;
|
||||
}
|
||||
|
||||
/// Update the cached click targets of the layers
|
||||
pub fn update_click_targets(&mut self, new_click_targets: HashMap<LayerNodeIdentifier, Vec<ClickTarget>>) {
|
||||
self.document_metadata.click_targets = new_click_targets;
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
use super::document_metadata::{DocumentMetadata, LayerNodeIdentifier};
|
||||
use super::network_interface::NodeNetworkInterface;
|
||||
use crate::messages::tool::common_functionality::graph_modification_utils;
|
||||
use glam::DVec2;
|
||||
use graph_craft::document::{NodeId, NodeNetwork};
|
||||
use serde::ser::SerializeStruct;
|
||||
|
||||
@@ -98,6 +100,22 @@ impl SelectedNodes {
|
||||
.filter(move |&layer| self.layer_visible(layer, network_interface) && !self.layer_locked(layer, network_interface))
|
||||
}
|
||||
|
||||
pub fn selected_visible_and_unlocked_layers_mean_average_origin<'a>(&'a self, network_interface: &'a NodeNetworkInterface) -> DVec2 {
|
||||
let (sum, count) = self
|
||||
.selected_visible_and_unlocked_layers(network_interface)
|
||||
.map(|layer| graph_modification_utils::get_viewport_origin(layer, network_interface))
|
||||
.fold((glam::DVec2::ZERO, 0), |(sum, count), item| (sum + item, count + 1));
|
||||
if count == 0 { DVec2::ZERO } else { sum / count as f64 }
|
||||
}
|
||||
|
||||
pub fn selected_visible_and_unlocked_median_points<'a>(&'a self, network_interface: &'a NodeNetworkInterface) -> DVec2 {
|
||||
let (sum, count) = self
|
||||
.selected_visible_and_unlocked_layers(network_interface)
|
||||
.map(|layer| graph_modification_utils::get_viewport_center(layer, network_interface))
|
||||
.fold((glam::DVec2::ZERO, 0), |(sum, count), item| (sum + item, count + 1));
|
||||
if count == 0 { DVec2::ZERO } else { sum / count as f64 }
|
||||
}
|
||||
|
||||
pub fn selected_layers<'a>(&'a self, metadata: &'a DocumentMetadata) -> impl Iterator<Item = LayerNodeIdentifier> + 'a {
|
||||
metadata.all_layers().filter(|layer| self.0.contains(&layer.to_node()))
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ use crate::messages::portfolio::document::graph_operation::transform_utils;
|
||||
use crate::messages::portfolio::document::graph_operation::utility_types::{ModifyInputsContext, TransformIn};
|
||||
use crate::messages::portfolio::document::utility_types::document_metadata::{DocumentMetadata, LayerNodeIdentifier};
|
||||
use crate::messages::prelude::*;
|
||||
use crate::messages::tool::common_functionality::graph_modification_utils;
|
||||
use crate::messages::tool::common_functionality::shape_editor::ShapeState;
|
||||
use crate::messages::tool::utility_types::ToolType;
|
||||
use glam::{DAffine2, DMat2, DVec2};
|
||||
@@ -537,17 +536,6 @@ impl<'a> Selected<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn mean_average_of_pivots(&mut self) -> DVec2 {
|
||||
let xy_summation = self
|
||||
.selected
|
||||
.iter()
|
||||
.map(|&layer| graph_modification_utils::get_viewport_pivot(layer, self.network_interface))
|
||||
.reduce(|a, b| a + b)
|
||||
.unwrap_or_default();
|
||||
|
||||
xy_summation / self.selected.len() as f64
|
||||
}
|
||||
|
||||
pub fn center_of_aabb(&mut self) -> DVec2 {
|
||||
let [min, max] = self
|
||||
.selected
|
||||
|
||||
@@ -7,6 +7,7 @@ use crate::messages::portfolio::document::utility_types::network_interface::{Inp
|
||||
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::text::TypesettingConfig;
|
||||
use graphene_std::uuid::NodeId;
|
||||
@@ -190,6 +191,8 @@ pub fn document_migration_reset_node_definition(document_serialized_content: &st
|
||||
}
|
||||
|
||||
pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_node_definitions_on_open: bool) {
|
||||
document.network_interface.migrate_path_modify_node();
|
||||
|
||||
let network = document.network_interface.document_network().clone();
|
||||
|
||||
// Apply string replacements to each node
|
||||
@@ -215,468 +218,476 @@ pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_
|
||||
.map(|(node_id, node, path)| (*node_id, node.clone(), path))
|
||||
.collect::<Vec<(NodeId, graph_craft::document::DocumentNode, Vec<NodeId>)>>();
|
||||
for (node_id, node, network_path) in &nodes {
|
||||
if reset_node_definitions_on_open {
|
||||
if let Some(Some(reference)) = document.network_interface.reference(node_id, network_path) {
|
||||
let Some(node_definition) = resolve_document_node_type(reference) else { continue };
|
||||
document.network_interface.replace_implementation(node_id, network_path, &mut node_definition.default_node_template());
|
||||
}
|
||||
migrate_node(node_id, node, network_path, document, reset_node_definitions_on_open);
|
||||
}
|
||||
}
|
||||
|
||||
fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId], document: &mut DocumentMessageHandler, reset_node_definitions_on_open: bool) -> Option<()> {
|
||||
if reset_node_definitions_on_open {
|
||||
if let Some(Some(reference)) = document.network_interface.reference(node_id, network_path) {
|
||||
let node_definition = resolve_document_node_type(reference)?;
|
||||
document.network_interface.replace_implementation(node_id, network_path, &mut node_definition.default_node_template());
|
||||
}
|
||||
}
|
||||
|
||||
// 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)) {
|
||||
document
|
||||
.network_interface
|
||||
.set_manual_compostion(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
|
||||
let reference = document.network_interface.reference(node_id, network_path).cloned().flatten()?;
|
||||
let reference = &reference;
|
||||
|
||||
let inputs_count = node.inputs.len();
|
||||
|
||||
// Upgrade Stroke node to reorder parameters and add "Align" and "Paint Order" (#2644)
|
||||
if reference == "Stroke" && inputs_count == 8 {
|
||||
let mut node_template = resolve_document_node_type(reference)?.default_node_template();
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?;
|
||||
|
||||
let align_input = NodeInput::value(TaggedValue::StrokeAlign(StrokeAlign::Center), false);
|
||||
let paint_order_input = NodeInput::value(TaggedValue::PaintOrder(PaintOrder::StrokeAbove), false);
|
||||
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path);
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 2), old_inputs[2].clone(), network_path);
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 3), align_input, network_path);
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 4), old_inputs[5].clone(), network_path);
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 5), old_inputs[6].clone(), network_path);
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 6), old_inputs[7].clone(), network_path);
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 7), paint_order_input, network_path);
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 8), old_inputs[3].clone(), network_path);
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 9), old_inputs[4].clone(), network_path);
|
||||
}
|
||||
|
||||
// Rename the old "Splines from Points" node to "Spline" and upgrade it to the new "Spline" node
|
||||
if reference == "Splines from Points" {
|
||||
document.network_interface.set_reference(node_id, network_path, Some("Spline".to_string()));
|
||||
}
|
||||
|
||||
// Upgrade the old "Spline" node to the new "Spline" node
|
||||
if reference == "Spline" {
|
||||
// Retrieve the proto node identifier and verify it is the old "Spline" node, otherwise skip it if this is the new "Spline" node
|
||||
let identifier = document
|
||||
.network_interface
|
||||
.implementation(node_id, network_path)
|
||||
.and_then(|implementation| implementation.get_proto_node());
|
||||
if identifier.map(|identifier| &identifier.name) != Some(&"graphene_core::vector::generator_nodes::SplineNode".into()) {
|
||||
return None;
|
||||
}
|
||||
|
||||
// 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)) {
|
||||
document
|
||||
.network_interface
|
||||
.set_manual_compostion(node_id, network_path, graph_craft::concrete!(graphene_std::Context).into());
|
||||
}
|
||||
|
||||
let Some(Some(reference)) = document.network_interface.reference(node_id, network_path).cloned() else {
|
||||
// Only nodes that have not been modified and still refer to a definition can be updated
|
||||
continue;
|
||||
// Obtain the document node for the given node ID, extract the vector points, and create vector data 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 reference = &reference;
|
||||
let vector_data = VectorData::from_subpath(Subpath::from_anchors_linear(points.to_vec(), false));
|
||||
|
||||
let inputs_count = node.inputs.len();
|
||||
// Retrieve the output connectors linked to the "Spline" node's output port
|
||||
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.");
|
||||
return None;
|
||||
};
|
||||
|
||||
// Upgrade Stroke node to reorder parameters and add "Align" and "Paint Order" (#2644)
|
||||
if reference == "Stroke" && inputs_count == 8 {
|
||||
let mut node_template = resolve_document_node_type(reference).unwrap().default_node_template();
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template).unwrap();
|
||||
// Get the node's current position in the graph
|
||||
let Some(node_position) = document.network_interface.position(node_id, network_path) else {
|
||||
log::error!("Could not get position of spline node.");
|
||||
return None;
|
||||
};
|
||||
|
||||
let align_input = NodeInput::value(TaggedValue::StrokeAlign(StrokeAlign::Center), false);
|
||||
let paint_order_input = NodeInput::value(TaggedValue::PaintOrder(PaintOrder::StrokeAbove), false);
|
||||
// Get the "Path" node definition and fill it in with the vector data 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::VectorModification(Default::default()), false)),
|
||||
]);
|
||||
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path);
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 2), old_inputs[2].clone(), network_path);
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 3), align_input, network_path);
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 4), old_inputs[5].clone(), network_path);
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 5), old_inputs[6].clone(), network_path);
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 6), old_inputs[7].clone(), network_path);
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 7), paint_order_input, network_path);
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 8), old_inputs[3].clone(), network_path);
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 9), old_inputs[4].clone(), network_path);
|
||||
// Get the "Spline" node definition and wire it up with the "Path" node as input
|
||||
let Some(spline_node_type) = resolve_document_node_type("Spline") else {
|
||||
log::error!("Spline node does not exist.");
|
||||
return None;
|
||||
};
|
||||
let spline_node = spline_node_type.node_template_input_override([Some(NodeInput::node(NodeId(1), 0))]);
|
||||
|
||||
// Create a new node group with the "Path" and "Spline" nodes and generate new node IDs for them
|
||||
let nodes = vec![(NodeId(1), path_node), (NodeId(0), spline_node)];
|
||||
let new_ids = nodes.iter().map(|(id, _)| (*id, NodeId::new())).collect::<HashMap<_, _>>();
|
||||
let new_spline_id = *new_ids.get(&NodeId(0))?;
|
||||
let new_path_id = *new_ids.get(&NodeId(1))?;
|
||||
|
||||
// Remove the old "Spline" node from the document
|
||||
document.network_interface.delete_nodes(vec![*node_id], false, network_path);
|
||||
|
||||
// Insert the new "Path" and "Spline" nodes into the network interface with generated IDs
|
||||
document.network_interface.insert_node_group(nodes.clone(), new_ids, network_path);
|
||||
|
||||
// Reposition the new "Spline" node to match the original "Spline" node's position
|
||||
document.network_interface.shift_node(&new_spline_id, node_position, network_path);
|
||||
|
||||
// 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
|
||||
for input_connector in spline_outputs {
|
||||
document.network_interface.set_input(&input_connector, NodeInput::node(new_spline_id, 0), network_path);
|
||||
}
|
||||
}
|
||||
|
||||
// Rename the old "Splines from Points" node to "Spline" and upgrade it to the new "Spline" node
|
||||
if reference == "Splines from Points" {
|
||||
document.network_interface.set_reference(node_id, network_path, Some("Spline".to_string()));
|
||||
// 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 != 9 {
|
||||
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)?;
|
||||
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path);
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 2), old_inputs[2].clone(), network_path);
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 3), old_inputs[3].clone(), network_path);
|
||||
document.network_interface.set_input(
|
||||
&InputConnector::node(*node_id, 4),
|
||||
if inputs_count == 6 {
|
||||
old_inputs[4].clone()
|
||||
} else {
|
||||
NodeInput::value(TaggedValue::F64(TypesettingConfig::default().line_height_ratio), false)
|
||||
},
|
||||
network_path,
|
||||
);
|
||||
document.network_interface.set_input(
|
||||
&InputConnector::node(*node_id, 5),
|
||||
if inputs_count == 6 {
|
||||
old_inputs[5].clone()
|
||||
} else {
|
||||
NodeInput::value(TaggedValue::F64(TypesettingConfig::default().character_spacing), false)
|
||||
},
|
||||
network_path,
|
||||
);
|
||||
document.network_interface.set_input(
|
||||
&InputConnector::node(*node_id, 6),
|
||||
if inputs_count >= 7 {
|
||||
old_inputs[6].clone()
|
||||
} else {
|
||||
NodeInput::value(TaggedValue::OptionalF64(TypesettingConfig::default().max_width), false)
|
||||
},
|
||||
network_path,
|
||||
);
|
||||
document.network_interface.set_input(
|
||||
&InputConnector::node(*node_id, 7),
|
||||
if inputs_count >= 8 {
|
||||
old_inputs[7].clone()
|
||||
} else {
|
||||
NodeInput::value(TaggedValue::OptionalF64(TypesettingConfig::default().max_height), false)
|
||||
},
|
||||
network_path,
|
||||
);
|
||||
document.network_interface.set_input(
|
||||
&InputConnector::node(*node_id, 8),
|
||||
if inputs_count >= 9 {
|
||||
old_inputs[8].clone()
|
||||
} else {
|
||||
NodeInput::value(TaggedValue::F64(TypesettingConfig::default().tilt), false)
|
||||
},
|
||||
network_path,
|
||||
);
|
||||
}
|
||||
|
||||
// Upgrade Sine, Cosine, and Tangent nodes to include a boolean input for whether the output should be in radians, which was previously the only option but is now not the default
|
||||
if (reference == "Sine" || reference == "Cosine" || reference == "Tangent") && inputs_count == 1 {
|
||||
let mut node_template = resolve_document_node_type(reference)?.default_node_template();
|
||||
document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
|
||||
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?;
|
||||
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
|
||||
document
|
||||
.network_interface
|
||||
.set_input(&InputConnector::node(*node_id, 1), NodeInput::value(TaggedValue::Bool(true), false), network_path);
|
||||
}
|
||||
|
||||
// Upgrade the Modulo node to include a boolean input for whether the output should be always positive, which was previously not an option
|
||||
if reference == "Modulo" && inputs_count == 2 {
|
||||
let mut node_template = resolve_document_node_type(reference)?.default_node_template();
|
||||
document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
|
||||
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?;
|
||||
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path);
|
||||
document
|
||||
.network_interface
|
||||
.set_input(&InputConnector::node(*node_id, 2), NodeInput::value(TaggedValue::Bool(false), false), network_path);
|
||||
}
|
||||
|
||||
// Upgrade the Mirror node to add the `keep_original` boolean input
|
||||
if reference == "Mirror" && inputs_count == 3 {
|
||||
let mut node_template = resolve_document_node_type(reference)?.default_node_template();
|
||||
document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
|
||||
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?;
|
||||
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path);
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 2), old_inputs[2].clone(), network_path);
|
||||
document
|
||||
.network_interface
|
||||
.set_input(&InputConnector::node(*node_id, 3), NodeInput::value(TaggedValue::Bool(true), false), network_path);
|
||||
}
|
||||
|
||||
// Upgrade the Mirror node to add the `reference_point` input and change `offset` from `DVec2` to `f64`
|
||||
if reference == "Mirror" && inputs_count == 4 {
|
||||
let mut node_template = resolve_document_node_type(reference)?.default_node_template();
|
||||
document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
|
||||
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?;
|
||||
|
||||
let Some(&TaggedValue::DVec2(old_offset)) = old_inputs[1].as_value() else { return None };
|
||||
let old_offset = if old_offset.x.abs() > old_offset.y.abs() { old_offset.x } else { old_offset.y };
|
||||
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
|
||||
document.network_interface.set_input(
|
||||
&InputConnector::node(*node_id, 1),
|
||||
NodeInput::value(TaggedValue::ReferencePoint(graphene_std::transform::ReferencePoint::Center), false),
|
||||
network_path,
|
||||
);
|
||||
document
|
||||
.network_interface
|
||||
.set_input(&InputConnector::node(*node_id, 2), NodeInput::value(TaggedValue::F64(old_offset), false), network_path);
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 3), old_inputs[2].clone(), network_path);
|
||||
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"
|
||||
if reference == "Artboard" && reset_node_definitions_on_open {
|
||||
let label = document.network_interface.display_name(node_id, network_path);
|
||||
document
|
||||
.network_interface
|
||||
.set_input(&InputConnector::node(NodeId(0), 1), NodeInput::value(TaggedValue::String(label), false), &[*node_id]);
|
||||
}
|
||||
|
||||
if reference == "Image" && inputs_count == 1 {
|
||||
let mut node_template = resolve_document_node_type(reference)?.default_node_template();
|
||||
document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
|
||||
|
||||
// Insert a new empty input for the image
|
||||
document.network_interface.add_import(TaggedValue::None, false, 0, "Empty", "", &[*node_id]);
|
||||
document.network_interface.set_reference(node_id, network_path, Some("Image".to_string()));
|
||||
}
|
||||
|
||||
if reference == "Noise Pattern" && inputs_count == 15 {
|
||||
let mut node_template = resolve_document_node_type(reference)?.default_node_template();
|
||||
document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
|
||||
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?;
|
||||
|
||||
document
|
||||
.network_interface
|
||||
.set_input(&InputConnector::node(*node_id, 0), NodeInput::value(TaggedValue::None, false), network_path);
|
||||
for (i, input) in old_inputs.iter().enumerate() {
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, i + 1), input.clone(), network_path);
|
||||
}
|
||||
}
|
||||
|
||||
// Upgrade the old "Spline" node to the new "Spline" node
|
||||
if reference == "Spline" {
|
||||
// Retrieve the proto node identifier and verify it is the old "Spline" node, otherwise skip it if this is the new "Spline" node
|
||||
let identifier = document
|
||||
.network_interface
|
||||
.implementation(node_id, network_path)
|
||||
.and_then(|implementation| implementation.get_proto_node());
|
||||
if identifier.map(|identifier| &identifier.name) != Some(&"graphene_core::vector::generator_nodes::SplineNode".into()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Obtain the document node for the given node ID, extract the vector points, and create vector data from the list of points
|
||||
let node = document.network_interface.document_node(node_id, network_path).unwrap();
|
||||
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");
|
||||
continue;
|
||||
};
|
||||
let vector_data = VectorData::from_subpath(Subpath::from_anchors_linear(points.to_vec(), false));
|
||||
|
||||
// Retrieve the output connectors linked to the "Spline" node's output port
|
||||
let spline_outputs = document
|
||||
.network_interface
|
||||
.outward_wires(network_path)
|
||||
.unwrap()
|
||||
.get(&OutputConnector::node(*node_id, 0))
|
||||
.expect("Vec of InputConnector Spline node is connected to its output port 0.")
|
||||
.clone();
|
||||
|
||||
// Get the node's current position in the graph
|
||||
let Some(node_position) = document.network_interface.position(node_id, network_path) else {
|
||||
log::error!("Could not get position of spline node.");
|
||||
continue;
|
||||
};
|
||||
|
||||
// Get the "Path" node definition and fill it in with the vector data and default vector modification
|
||||
let path_node_type = resolve_document_node_type("Path").expect("Path node does not exist.");
|
||||
let path_node = path_node_type.node_template_input_override([
|
||||
Some(NodeInput::value(TaggedValue::VectorData(VectorDataTable::new(vector_data)), true)),
|
||||
Some(NodeInput::value(TaggedValue::VectorModification(Default::default()), false)),
|
||||
]);
|
||||
|
||||
// Get the "Spline" node definition and wire it up with the "Path" node as input
|
||||
let spline_node_type = resolve_document_node_type("Spline").expect("Spline node does not exist.");
|
||||
let spline_node = spline_node_type.node_template_input_override([Some(NodeInput::node(NodeId(1), 0))]);
|
||||
|
||||
// Create a new node group with the "Path" and "Spline" nodes and generate new node IDs for them
|
||||
let nodes = vec![(NodeId(1), path_node), (NodeId(0), spline_node)];
|
||||
let new_ids = nodes.iter().map(|(id, _)| (*id, NodeId::new())).collect::<HashMap<_, _>>();
|
||||
let new_spline_id = *new_ids.get(&NodeId(0)).unwrap();
|
||||
let new_path_id = *new_ids.get(&NodeId(1)).unwrap();
|
||||
|
||||
// Remove the old "Spline" node from the document
|
||||
document.network_interface.delete_nodes(vec![*node_id], false, network_path);
|
||||
|
||||
// Insert the new "Path" and "Spline" nodes into the network interface with generated IDs
|
||||
document.network_interface.insert_node_group(nodes.clone(), new_ids, network_path);
|
||||
|
||||
// Reposition the new "Spline" node to match the original "Spline" node's position
|
||||
document.network_interface.shift_node(&new_spline_id, node_position, network_path);
|
||||
|
||||
// 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
|
||||
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 != 9 {
|
||||
let mut template = resolve_document_node_type(reference).unwrap().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).unwrap();
|
||||
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path);
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 2), old_inputs[2].clone(), network_path);
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 3), old_inputs[3].clone(), network_path);
|
||||
document.network_interface.set_input(
|
||||
&InputConnector::node(*node_id, 4),
|
||||
if inputs_count == 6 {
|
||||
old_inputs[4].clone()
|
||||
} else {
|
||||
NodeInput::value(TaggedValue::F64(TypesettingConfig::default().line_height_ratio), false)
|
||||
},
|
||||
network_path,
|
||||
);
|
||||
document.network_interface.set_input(
|
||||
&InputConnector::node(*node_id, 5),
|
||||
if inputs_count == 6 {
|
||||
old_inputs[5].clone()
|
||||
} else {
|
||||
NodeInput::value(TaggedValue::F64(TypesettingConfig::default().character_spacing), false)
|
||||
},
|
||||
network_path,
|
||||
);
|
||||
document.network_interface.set_input(
|
||||
&InputConnector::node(*node_id, 6),
|
||||
if inputs_count >= 7 {
|
||||
old_inputs[6].clone()
|
||||
} else {
|
||||
NodeInput::value(TaggedValue::OptionalF64(TypesettingConfig::default().max_width), false)
|
||||
},
|
||||
network_path,
|
||||
);
|
||||
document.network_interface.set_input(
|
||||
&InputConnector::node(*node_id, 7),
|
||||
if inputs_count >= 8 {
|
||||
old_inputs[7].clone()
|
||||
} else {
|
||||
NodeInput::value(TaggedValue::OptionalF64(TypesettingConfig::default().max_height), false)
|
||||
},
|
||||
network_path,
|
||||
);
|
||||
document.network_interface.set_input(
|
||||
&InputConnector::node(*node_id, 8),
|
||||
if inputs_count >= 9 {
|
||||
old_inputs[8].clone()
|
||||
} else {
|
||||
NodeInput::value(TaggedValue::F64(TypesettingConfig::default().tilt), false)
|
||||
},
|
||||
network_path,
|
||||
);
|
||||
}
|
||||
|
||||
// Upgrade Sine, Cosine, and Tangent nodes to include a boolean input for whether the output should be in radians, which was previously the only option but is now not the default
|
||||
if (reference == "Sine" || reference == "Cosine" || reference == "Tangent") && inputs_count == 1 {
|
||||
let mut node_template = resolve_document_node_type(reference).unwrap().default_node_template();
|
||||
document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
|
||||
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template).unwrap();
|
||||
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
|
||||
document
|
||||
.network_interface
|
||||
.set_input(&InputConnector::node(*node_id, 1), NodeInput::value(TaggedValue::Bool(true), false), network_path);
|
||||
}
|
||||
|
||||
// Upgrade the Modulo node to include a boolean input for whether the output should be always positive, which was previously not an option
|
||||
if reference == "Modulo" && inputs_count == 2 {
|
||||
let mut node_template = resolve_document_node_type(reference).unwrap().default_node_template();
|
||||
document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
|
||||
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template).unwrap();
|
||||
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path);
|
||||
document
|
||||
.network_interface
|
||||
.set_input(&InputConnector::node(*node_id, 2), NodeInput::value(TaggedValue::Bool(false), false), network_path);
|
||||
}
|
||||
|
||||
// Upgrade the Mirror node to add the `keep_original` boolean input
|
||||
if reference == "Mirror" && inputs_count == 3 {
|
||||
let mut node_template = resolve_document_node_type(reference).unwrap().default_node_template();
|
||||
document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
|
||||
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template).unwrap();
|
||||
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path);
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 2), old_inputs[2].clone(), network_path);
|
||||
document
|
||||
.network_interface
|
||||
.set_input(&InputConnector::node(*node_id, 3), NodeInput::value(TaggedValue::Bool(true), false), network_path);
|
||||
}
|
||||
|
||||
// Upgrade the Mirror node to add the `reference_point` input and change `offset` from `DVec2` to `f64`
|
||||
if reference == "Mirror" && inputs_count == 4 {
|
||||
let mut node_template = resolve_document_node_type(reference).unwrap().default_node_template();
|
||||
document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
|
||||
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template).unwrap();
|
||||
|
||||
let Some(&TaggedValue::DVec2(old_offset)) = old_inputs[1].as_value() else { return };
|
||||
let old_offset = if old_offset.x.abs() > old_offset.y.abs() { old_offset.x } else { old_offset.y };
|
||||
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
|
||||
document.network_interface.set_input(
|
||||
&InputConnector::node(*node_id, 1),
|
||||
NodeInput::value(TaggedValue::ReferencePoint(graphene_std::transform::ReferencePoint::Center), false),
|
||||
network_path,
|
||||
);
|
||||
document
|
||||
.network_interface
|
||||
.set_input(&InputConnector::node(*node_id, 2), NodeInput::value(TaggedValue::F64(old_offset), false), network_path);
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 3), old_inputs[2].clone(), network_path);
|
||||
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"
|
||||
if reference == "Artboard" && reset_node_definitions_on_open {
|
||||
let label = document.network_interface.display_name(node_id, network_path);
|
||||
document
|
||||
.network_interface
|
||||
.set_input(&InputConnector::node(NodeId(0), 1), NodeInput::value(TaggedValue::String(label), false), &[*node_id]);
|
||||
}
|
||||
|
||||
if reference == "Image" && inputs_count == 1 {
|
||||
let mut node_template = resolve_document_node_type(reference).unwrap().default_node_template();
|
||||
document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
|
||||
|
||||
// Insert a new empty input for the image
|
||||
document.network_interface.add_import(TaggedValue::None, false, 0, "Empty", "", &[*node_id]);
|
||||
document.network_interface.set_reference(node_id, network_path, Some("Image".to_string()));
|
||||
}
|
||||
|
||||
if reference == "Noise Pattern" && inputs_count == 15 {
|
||||
let mut node_template = resolve_document_node_type(reference).unwrap().default_node_template();
|
||||
document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
|
||||
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template).unwrap();
|
||||
|
||||
document
|
||||
.network_interface
|
||||
.set_input(&InputConnector::node(*node_id, 0), NodeInput::value(TaggedValue::None, false), network_path);
|
||||
for (i, input) in old_inputs.iter().enumerate() {
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, i + 1), input.clone(), network_path);
|
||||
}
|
||||
}
|
||||
|
||||
if reference == "Instance on Points" && inputs_count == 2 {
|
||||
let mut node_template = resolve_document_node_type(reference).unwrap().default_node_template();
|
||||
document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
|
||||
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template).unwrap();
|
||||
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path);
|
||||
}
|
||||
|
||||
if reference == "Morph" && inputs_count == 4 {
|
||||
let mut node_template = resolve_document_node_type(reference).unwrap().default_node_template();
|
||||
document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
|
||||
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template).unwrap();
|
||||
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path);
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 2), old_inputs[2].clone(), network_path);
|
||||
// We have removed the last input, so we don't add index 3
|
||||
}
|
||||
|
||||
if reference == "Brush" && inputs_count == 4 {
|
||||
let mut node_template = resolve_document_node_type(reference).unwrap().default_node_template();
|
||||
document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
|
||||
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template).unwrap();
|
||||
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
|
||||
// We have removed the second input ("bounds"), so we don't add index 1 and we shift the rest of the inputs down by one
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[2].clone(), network_path);
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 2), old_inputs[3].clone(), network_path);
|
||||
}
|
||||
|
||||
if reference == "Flatten Vector Elements" {
|
||||
let mut node_template = resolve_document_node_type(reference).unwrap().default_node_template();
|
||||
document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
|
||||
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template).unwrap();
|
||||
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
|
||||
|
||||
document.network_interface.replace_reference_name(node_id, network_path, "Flatten Path".to_string());
|
||||
}
|
||||
|
||||
if reference == "Remove Handles" {
|
||||
let mut node_template = resolve_document_node_type(reference).unwrap().default_node_template();
|
||||
document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
|
||||
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template).unwrap();
|
||||
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
|
||||
document
|
||||
.network_interface
|
||||
.set_input(&InputConnector::node(*node_id, 1), NodeInput::value(TaggedValue::F64(0.), false), network_path);
|
||||
document
|
||||
.network_interface
|
||||
.set_input(&InputConnector::node(*node_id, 2), NodeInput::value(TaggedValue::Bool(false), false), network_path);
|
||||
|
||||
document.network_interface.replace_reference_name(node_id, network_path, "Auto-Tangents".to_string());
|
||||
}
|
||||
|
||||
if reference == "Generate Handles" {
|
||||
let mut node_template = resolve_document_node_type("Auto-Tangents").unwrap().default_node_template();
|
||||
document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
|
||||
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template).unwrap();
|
||||
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path);
|
||||
document
|
||||
.network_interface
|
||||
.set_input(&InputConnector::node(*node_id, 2), NodeInput::value(TaggedValue::Bool(true), false), network_path);
|
||||
|
||||
document.network_interface.replace_reference_name(node_id, network_path, "Auto-Tangents".to_string());
|
||||
}
|
||||
|
||||
if reference == "Merge by Distance" && inputs_count == 2 {
|
||||
let mut node_template = resolve_document_node_type(reference).unwrap().default_node_template();
|
||||
document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
|
||||
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template).unwrap();
|
||||
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path);
|
||||
document.network_interface.set_input(
|
||||
&InputConnector::node(*node_id, 2),
|
||||
NodeInput::value(TaggedValue::MergeByDistanceAlgorithm(graphene_std::vector::misc::MergeByDistanceAlgorithm::Topological), false),
|
||||
network_path,
|
||||
);
|
||||
}
|
||||
|
||||
if reference == "Spatial Merge by Distance" {
|
||||
let mut node_template = resolve_document_node_type("Merge by Distance").unwrap().default_node_template();
|
||||
document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
|
||||
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template).unwrap();
|
||||
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path);
|
||||
document.network_interface.set_input(
|
||||
&InputConnector::node(*node_id, 2),
|
||||
NodeInput::value(TaggedValue::MergeByDistanceAlgorithm(graphene_std::vector::misc::MergeByDistanceAlgorithm::Spatial), false),
|
||||
network_path,
|
||||
);
|
||||
|
||||
document.network_interface.replace_reference_name(node_id, network_path, "Merge by Distance".to_string());
|
||||
}
|
||||
|
||||
if reference == "Sample Points" && inputs_count == 5 {
|
||||
let mut node_template = resolve_document_node_type("Sample Polyline").unwrap().default_node_template();
|
||||
document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
|
||||
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template).unwrap();
|
||||
let new_spacing_value = NodeInput::value(TaggedValue::PointSpacingType(graphene_std::vector::misc::PointSpacingType::Separation), false);
|
||||
let new_quantity_value = NodeInput::value(TaggedValue::U32(100), false);
|
||||
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 1), new_spacing_value, network_path);
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 2), old_inputs[1].clone(), network_path);
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 3), new_quantity_value, network_path);
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 4), old_inputs[2].clone(), network_path);
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 5), old_inputs[3].clone(), network_path);
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 6), old_inputs[4].clone(), network_path);
|
||||
|
||||
document.network_interface.replace_reference_name(node_id, network_path, "Sample Polyline".to_string());
|
||||
}
|
||||
|
||||
// Make the "Quantity" parameter a u32 instead of f64
|
||||
if reference == "Sample Polyline" {
|
||||
// Get the inputs, obtain the quantity value, and put the inputs back
|
||||
let quantity_value = document
|
||||
.network_interface
|
||||
.input_from_connector(&InputConnector::Node { node_id: *node_id, input_index: 3 }, network_path)
|
||||
.unwrap();
|
||||
|
||||
if let NodeInput::Value { tagged_value, exposed } = quantity_value {
|
||||
if let TaggedValue::F64(value) = **tagged_value {
|
||||
let new_quantity_value = NodeInput::value(TaggedValue::U32(value as u32), *exposed);
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 3), new_quantity_value, network_path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Make the "Grid" node, if its input of index 3 is a DVec2 for "angles" instead of a u32 for the "columns" input that now succeeds "angles", move the angle to index 5 (after "columns" and "rows")
|
||||
if reference == "Grid" && inputs_count == 6 {
|
||||
let node_definition = resolve_document_node_type(reference).unwrap();
|
||||
let mut new_node_template = node_definition.default_node_template();
|
||||
|
||||
let mut current_node_template = document.network_interface.create_node_template(node_id, network_path).unwrap();
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut new_node_template).unwrap();
|
||||
let index_3_value = old_inputs.get(3).cloned();
|
||||
|
||||
if let Some(NodeInput::Value { tagged_value, exposed: _ }) = index_3_value {
|
||||
if matches!(*tagged_value, TaggedValue::DVec2(_)) {
|
||||
// Move index 3 to the end
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path);
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 2), old_inputs[2].clone(), network_path);
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 3), old_inputs[4].clone(), network_path);
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 4), old_inputs[5].clone(), network_path);
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 5), old_inputs[3].clone(), network_path);
|
||||
} else {
|
||||
// Swap it back if we're not changing anything
|
||||
let _ = document.network_interface.replace_inputs(node_id, network_path, &mut current_node_template);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure layers are positioned as stacks if they are upstream siblings of another layer
|
||||
document.network_interface.load_structure();
|
||||
let all_layers = LayerNodeIdentifier::ROOT_PARENT.descendants(document.network_interface.document_metadata()).collect::<Vec<_>>();
|
||||
for layer in all_layers {
|
||||
let Some((downstream_node, input_index)) = document
|
||||
.network_interface
|
||||
.outward_wires(&[])
|
||||
.and_then(|outward_wires| outward_wires.get(&OutputConnector::node(layer.to_node(), 0)))
|
||||
.and_then(|outward_wires| outward_wires.first())
|
||||
.and_then(|input_connector| input_connector.node_id().map(|node_id| (node_id, input_connector.input_index())))
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
// If the downstream node is a layer and the input is the first input and the current layer is not in a stack
|
||||
if input_index == 0 && document.network_interface.is_layer(&downstream_node, &[]) && !document.network_interface.is_stack(&layer.to_node(), &[]) {
|
||||
// Ensure the layer is horizontally aligned with the downstream layer to prevent changing the layout of old files
|
||||
let (Some(layer_position), Some(downstream_position)) = (document.network_interface.position(&layer.to_node(), &[]), document.network_interface.position(&downstream_node, &[])) else {
|
||||
log::error!("Could not get position for layer {:?} or downstream node {} when opening file", layer.to_node(), downstream_node);
|
||||
continue;
|
||||
};
|
||||
if layer_position.x == downstream_position.x {
|
||||
document.network_interface.set_stack_position_calculated_offset(&layer.to_node(), &downstream_node, &[]);
|
||||
}
|
||||
if reference == "Instance on Points" && inputs_count == 2 {
|
||||
let mut node_template = resolve_document_node_type(reference)?.default_node_template();
|
||||
document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
|
||||
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?;
|
||||
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path);
|
||||
}
|
||||
|
||||
if reference == "Morph" && inputs_count == 4 {
|
||||
let mut node_template = resolve_document_node_type(reference)?.default_node_template();
|
||||
document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
|
||||
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?;
|
||||
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path);
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 2), old_inputs[2].clone(), network_path);
|
||||
// We have removed the last input, so we don't add index 3
|
||||
}
|
||||
|
||||
if reference == "Brush" && inputs_count == 4 {
|
||||
let mut node_template = resolve_document_node_type(reference)?.default_node_template();
|
||||
document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
|
||||
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?;
|
||||
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
|
||||
// We have removed the second input ("bounds"), so we don't add index 1 and we shift the rest of the inputs down by one
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[2].clone(), network_path);
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 2), old_inputs[3].clone(), network_path);
|
||||
}
|
||||
|
||||
if reference == "Flatten Vector Elements" {
|
||||
let mut node_template = resolve_document_node_type(reference)?.default_node_template();
|
||||
document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
|
||||
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?;
|
||||
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
|
||||
|
||||
document.network_interface.replace_reference_name(node_id, network_path, "Flatten Path".to_string());
|
||||
}
|
||||
|
||||
if reference == "Remove Handles" {
|
||||
let mut node_template = resolve_document_node_type(reference)?.default_node_template();
|
||||
document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
|
||||
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?;
|
||||
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
|
||||
document
|
||||
.network_interface
|
||||
.set_input(&InputConnector::node(*node_id, 1), NodeInput::value(TaggedValue::F64(0.), false), network_path);
|
||||
document
|
||||
.network_interface
|
||||
.set_input(&InputConnector::node(*node_id, 2), NodeInput::value(TaggedValue::Bool(false), false), network_path);
|
||||
|
||||
document.network_interface.replace_reference_name(node_id, network_path, "Auto-Tangents".to_string());
|
||||
}
|
||||
|
||||
if reference == "Generate Handles" {
|
||||
let mut node_template = resolve_document_node_type("Auto-Tangents")?.default_node_template();
|
||||
document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
|
||||
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?;
|
||||
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path);
|
||||
document
|
||||
.network_interface
|
||||
.set_input(&InputConnector::node(*node_id, 2), NodeInput::value(TaggedValue::Bool(true), false), network_path);
|
||||
|
||||
document.network_interface.replace_reference_name(node_id, network_path, "Auto-Tangents".to_string());
|
||||
}
|
||||
|
||||
if reference == "Merge by Distance" && inputs_count == 2 {
|
||||
let mut node_template = resolve_document_node_type(reference)?.default_node_template();
|
||||
document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
|
||||
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?;
|
||||
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path);
|
||||
document.network_interface.set_input(
|
||||
&InputConnector::node(*node_id, 2),
|
||||
NodeInput::value(TaggedValue::MergeByDistanceAlgorithm(graphene_std::vector::misc::MergeByDistanceAlgorithm::Topological), false),
|
||||
network_path,
|
||||
);
|
||||
}
|
||||
|
||||
if reference == "Spatial Merge by Distance" {
|
||||
let mut node_template = resolve_document_node_type("Merge by Distance")?.default_node_template();
|
||||
document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
|
||||
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?;
|
||||
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path);
|
||||
document.network_interface.set_input(
|
||||
&InputConnector::node(*node_id, 2),
|
||||
NodeInput::value(TaggedValue::MergeByDistanceAlgorithm(graphene_std::vector::misc::MergeByDistanceAlgorithm::Spatial), false),
|
||||
network_path,
|
||||
);
|
||||
|
||||
document.network_interface.replace_reference_name(node_id, network_path, "Merge by Distance".to_string());
|
||||
}
|
||||
|
||||
if reference == "Sample Points" && inputs_count == 5 {
|
||||
let mut node_template = resolve_document_node_type("Sample Polyline")?.default_node_template();
|
||||
document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
|
||||
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?;
|
||||
let new_spacing_value = NodeInput::value(TaggedValue::PointSpacingType(graphene_std::vector::misc::PointSpacingType::Separation), false);
|
||||
let new_quantity_value = NodeInput::value(TaggedValue::U32(100), false);
|
||||
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 1), new_spacing_value, network_path);
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 2), old_inputs[1].clone(), network_path);
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 3), new_quantity_value, network_path);
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 4), old_inputs[2].clone(), network_path);
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 5), old_inputs[3].clone(), network_path);
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 6), old_inputs[4].clone(), network_path);
|
||||
|
||||
document.network_interface.replace_reference_name(node_id, network_path, "Sample Polyline".to_string());
|
||||
}
|
||||
|
||||
// Make the "Quantity" parameter a u32 instead of f64
|
||||
if reference == "Sample Polyline" {
|
||||
// Get the inputs, obtain the quantity value, and put the inputs back
|
||||
let quantity_value = document
|
||||
.network_interface
|
||||
.input_from_connector(&InputConnector::Node { node_id: *node_id, input_index: 3 }, network_path)?;
|
||||
|
||||
if let NodeInput::Value { tagged_value, exposed } = quantity_value {
|
||||
if let TaggedValue::F64(value) = **tagged_value {
|
||||
let new_quantity_value = NodeInput::value(TaggedValue::U32(value as u32), *exposed);
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 3), new_quantity_value, network_path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Make the "Grid" node, if its input of index 3 is a DVec2 for "angles" instead of a u32 for the "columns" input that now succeeds "angles", move the angle to index 5 (after "columns" and "rows")
|
||||
if reference == "Grid" && inputs_count == 6 {
|
||||
let node_definition = resolve_document_node_type(reference)?;
|
||||
let mut new_node_template = node_definition.default_node_template();
|
||||
|
||||
let mut current_node_template = document.network_interface.create_node_template(node_id, network_path)?;
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut new_node_template)?;
|
||||
let index_3_value = old_inputs.get(3).cloned();
|
||||
|
||||
let mut upgraded = false;
|
||||
|
||||
if let Some(NodeInput::Value { tagged_value, exposed: _ }) = index_3_value {
|
||||
if matches!(*tagged_value, TaggedValue::DVec2(_)) {
|
||||
// Move index 3 to the end
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path);
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 2), old_inputs[2].clone(), network_path);
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 3), old_inputs[4].clone(), network_path);
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 4), old_inputs[5].clone(), network_path);
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 5), old_inputs[3].clone(), network_path);
|
||||
|
||||
upgraded = true;
|
||||
}
|
||||
}
|
||||
|
||||
if !upgraded {
|
||||
let _ = document.network_interface.replace_inputs(node_id, network_path, &mut current_node_template);
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure layers are positioned as stacks if they are upstream siblings of another layer
|
||||
document.network_interface.load_structure();
|
||||
let all_layers = LayerNodeIdentifier::ROOT_PARENT.descendants(document.network_interface.document_metadata()).collect::<Vec<_>>();
|
||||
for layer in all_layers {
|
||||
let (downstream_node, input_index) = document
|
||||
.network_interface
|
||||
.outward_wires(&[])
|
||||
.and_then(|outward_wires| outward_wires.get(&OutputConnector::node(layer.to_node(), 0)))
|
||||
.and_then(|outward_wires| outward_wires.first())
|
||||
.and_then(|input_connector| input_connector.node_id().map(|node_id| (node_id, input_connector.input_index())))?;
|
||||
// If the downstream node is a layer and the input is the first input and the current layer is not in a stack
|
||||
if input_index == 0 && document.network_interface.is_layer(&downstream_node, &[]) && !document.network_interface.is_stack(&layer.to_node(), &[]) {
|
||||
// Ensure the layer is horizontally aligned with the downstream layer to prevent changing the layout of old files
|
||||
let (Some(layer_position), Some(downstream_position)) = (document.network_interface.position(&layer.to_node(), &[]), document.network_interface.position(&downstream_node, &[])) else {
|
||||
log::error!("Could not get position for layer {:?} or downstream node {} when opening file", layer.to_node(), downstream_node);
|
||||
return None;
|
||||
};
|
||||
if layer_position.x == downstream_position.x {
|
||||
document.network_interface.set_stack_position_calculated_offset(&layer.to_node(), &downstream_node, &[]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some(())
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ use crate::messages::portfolio::document::utility_types::misc::{AlignAggregate,
|
||||
use crate::messages::prelude::*;
|
||||
use graphene_std::path_bool::BooleanOperation;
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
#[derive(Debug, Clone, Default, ExtractField)]
|
||||
pub struct MenuBarMessageHandler {
|
||||
pub has_active_document: bool,
|
||||
pub canvas_tilted: bool,
|
||||
@@ -21,6 +21,7 @@ pub struct MenuBarMessageHandler {
|
||||
pub reset_node_definitions_on_open: bool,
|
||||
}
|
||||
|
||||
#[message_handler_data]
|
||||
impl MessageHandler<MenuBarMessage, ()> for MenuBarMessageHandler {
|
||||
fn process_message(&mut self, message: MenuBarMessage, responses: &mut VecDeque<Message>, _data: ()) {
|
||||
match message {
|
||||
|
||||
@@ -25,6 +25,7 @@ use graphene_std::renderer::Quad;
|
||||
use graphene_std::text::Font;
|
||||
use std::vec;
|
||||
|
||||
#[derive(ExtractField)]
|
||||
pub struct PortfolioMessageData<'a> {
|
||||
pub ipp: &'a InputPreprocessorMessageHandler,
|
||||
pub preferences: &'a PreferencesMessageHandler,
|
||||
@@ -35,7 +36,7 @@ pub struct PortfolioMessageData<'a> {
|
||||
pub animation: &'a AnimationMessageHandler,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
#[derive(Debug, Default, ExtractField)]
|
||||
pub struct PortfolioMessageHandler {
|
||||
menu_bar_message_handler: MenuBarMessageHandler,
|
||||
pub documents: HashMap<DocumentId, DocumentMessageHandler>,
|
||||
@@ -52,6 +53,7 @@ pub struct PortfolioMessageHandler {
|
||||
pub reset_node_definitions_on_open: bool,
|
||||
}
|
||||
|
||||
#[message_handler_data]
|
||||
impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMessageHandler {
|
||||
fn process_message(&mut self, message: PortfolioMessage, responses: &mut VecDeque<Message>, data: PortfolioMessageData) {
|
||||
let PortfolioMessageData {
|
||||
|
||||
@@ -15,7 +15,7 @@ use std::any::Any;
|
||||
use std::sync::Arc;
|
||||
|
||||
/// The spreadsheet UI allows for instance data to be previewed.
|
||||
#[derive(Default, Debug, Clone)]
|
||||
#[derive(Default, Debug, Clone, ExtractField)]
|
||||
pub struct SpreadsheetMessageHandler {
|
||||
/// Sets whether or not the spreadsheet is drawn.
|
||||
pub spreadsheet_view_open: bool,
|
||||
@@ -25,6 +25,7 @@ pub struct SpreadsheetMessageHandler {
|
||||
viewing_vector_data_domain: VectorDataDomain,
|
||||
}
|
||||
|
||||
#[message_handler_data]
|
||||
impl MessageHandler<SpreadsheetMessage, ()> for SpreadsheetMessageHandler {
|
||||
fn process_message(&mut self, message: SpreadsheetMessage, responses: &mut VecDeque<Message>, _data: ()) {
|
||||
match message {
|
||||
|
||||
@@ -5,7 +5,7 @@ use crate::messages::preferences::SelectionMode;
|
||||
use crate::messages::prelude::*;
|
||||
use graph_craft::wasm_application_io::EditorPreferences;
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, serde::Serialize, serde::Deserialize, specta::Type)]
|
||||
#[derive(Debug, PartialEq, Clone, serde::Serialize, serde::Deserialize, specta::Type, ExtractField)]
|
||||
pub struct PreferencesMessageHandler {
|
||||
pub selection_mode: SelectionMode,
|
||||
pub zoom_with_scroll: bool,
|
||||
@@ -44,6 +44,7 @@ impl Default for PreferencesMessageHandler {
|
||||
}
|
||||
}
|
||||
|
||||
#[message_handler_data]
|
||||
impl MessageHandler<PreferencesMessage, ()> for PreferencesMessageHandler {
|
||||
fn process_message(&mut self, message: PreferencesMessage, responses: &mut VecDeque<Message>, _data: ()) {
|
||||
match message {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// Root
|
||||
pub use crate::utility_traits::{ActionList, AsMessage, MessageHandler, ToDiscriminant, TransitiveChild};
|
||||
|
||||
pub use crate::utility_traits::{ActionList, AsMessage, 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::broadcast::{BroadcastMessage, BroadcastMessageDiscriminant, BroadcastMessageHandler};
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
use crate::consts::{COMPASS_ROSE_ARROW_CLICK_TARGET_ANGLE, COMPASS_ROSE_HOVER_RING_DIAMETER, COMPASS_ROSE_RING_INNER_DIAMETER};
|
||||
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
||||
use crate::messages::prelude::DocumentMessageHandler;
|
||||
use glam::{DAffine2, DVec2};
|
||||
use std::f64::consts::FRAC_PI_2;
|
||||
@@ -10,25 +9,32 @@ pub struct CompassRose {
|
||||
}
|
||||
|
||||
impl CompassRose {
|
||||
fn get_layer_pivot_transform(layer: LayerNodeIdentifier, document: &DocumentMessageHandler) -> DAffine2 {
|
||||
let [min, max] = document.metadata().nonzero_bounding_box(layer);
|
||||
|
||||
let bounds_transform = DAffine2::from_translation(min) * DAffine2::from_scale(max - min);
|
||||
let layer_transform = document.metadata().transform_to_viewport(layer);
|
||||
layer_transform * bounds_transform
|
||||
}
|
||||
pub fn refresh_position(&mut self, document: &DocumentMessageHandler) {
|
||||
let selected_nodes = document.network_interface.selected_nodes();
|
||||
let mut layers = selected_nodes.selected_visible_and_unlocked_layers(&document.network_interface);
|
||||
let selected = document.network_interface.selected_nodes();
|
||||
|
||||
let Some(first) = layers.next() else { return };
|
||||
let count = layers.count() + 1;
|
||||
let transform = if count == 1 {
|
||||
Self::get_layer_pivot_transform(first, document)
|
||||
} else {
|
||||
let [min, max] = document.selected_visible_and_unlock_layers_bounding_box_viewport().unwrap_or([DVec2::ZERO, DVec2::ONE]);
|
||||
DAffine2::from_translation(min) * DAffine2::from_scale(max - min)
|
||||
};
|
||||
if !selected.has_selected_nodes() {
|
||||
return;
|
||||
}
|
||||
|
||||
let transform = selected
|
||||
.selected_visible_and_unlocked_layers(&document.network_interface)
|
||||
.find(|layer| !document.network_interface.is_artboard(&layer.to_node(), &[]))
|
||||
.map(|layer| document.metadata().transform_to_viewport_with_first_transform_node_if_group(layer, &document.network_interface))
|
||||
.unwrap_or_default();
|
||||
|
||||
let bounds = document
|
||||
.network_interface
|
||||
.selected_nodes()
|
||||
.selected_visible_and_unlocked_layers(&document.network_interface)
|
||||
.filter_map(|layer| {
|
||||
document
|
||||
.metadata()
|
||||
.bounding_box_with_transform(layer, transform.inverse() * document.metadata().transform_to_viewport(layer))
|
||||
})
|
||||
.reduce(graphene_std::renderer::Quad::combine_bounds);
|
||||
|
||||
let [min, max] = bounds.unwrap_or([DVec2::ZERO, DVec2::ONE]);
|
||||
let transform = transform * DAffine2::from_translation(min) * DAffine2::from_scale(max - min);
|
||||
|
||||
self.compass_center = transform.transform_point2(DVec2::splat(0.5));
|
||||
}
|
||||
|
||||
@@ -5,9 +5,9 @@ use crate::messages::portfolio::document::utility_types::network_interface::{Flo
|
||||
use crate::messages::prelude::*;
|
||||
use bezier_rs::Subpath;
|
||||
use glam::DVec2;
|
||||
use graph_craft::concrete;
|
||||
use graph_craft::document::value::TaggedValue;
|
||||
use graph_craft::document::{NodeId, NodeInput};
|
||||
use graph_craft::{ProtoNodeIdentifier, concrete};
|
||||
use graphene_std::Color;
|
||||
use graphene_std::NodeInputDecleration;
|
||||
use graphene_std::raster::BlendMode;
|
||||
@@ -243,20 +243,26 @@ pub fn new_custom(id: NodeId, nodes: Vec<(NodeId, NodeTemplate)>, parent: LayerN
|
||||
LayerNodeIdentifier::new_unchecked(id)
|
||||
}
|
||||
|
||||
/// Locate the final pivot from the transform (TODO: decide how the pivot should actually work)
|
||||
pub fn get_pivot(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<DVec2> {
|
||||
let pivot_node_input_index = 5;
|
||||
if let TaggedValue::DVec2(pivot) = NodeGraphLayer::new(layer, network_interface).find_input("Transform", pivot_node_input_index)? {
|
||||
Some(*pivot)
|
||||
/// Locate the origin of the transform node
|
||||
pub fn get_origin(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<DVec2> {
|
||||
use graphene_std::transform_nodes::transform::TranslateInput;
|
||||
|
||||
if let TaggedValue::DVec2(origin) = NodeGraphLayer::new(layer, network_interface).find_input("Transform", TranslateInput::INDEX)? {
|
||||
Some(*origin)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_viewport_pivot(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> DVec2 {
|
||||
pub fn get_viewport_origin(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> DVec2 {
|
||||
let origin = get_origin(layer, network_interface).unwrap_or_default();
|
||||
network_interface.document_metadata().document_to_viewport.transform_point2(origin)
|
||||
}
|
||||
|
||||
pub fn get_viewport_center(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> DVec2 {
|
||||
let [min, max] = network_interface.document_metadata().nonzero_bounding_box(layer);
|
||||
let pivot = get_pivot(layer, network_interface).unwrap_or(DVec2::splat(0.5));
|
||||
network_interface.document_metadata().transform_to_viewport(layer).transform_point2(min + (max - min) * pivot)
|
||||
let center = DVec2::splat(0.5);
|
||||
network_interface.document_metadata().transform_to_viewport(layer).transform_point2(min + (max - min) * center)
|
||||
}
|
||||
|
||||
/// Get the current gradient of a layer from the closest "Fill" node.
|
||||
@@ -416,14 +422,14 @@ impl<'a> NodeGraphLayer<'a> {
|
||||
}
|
||||
|
||||
/// Node id of a protonode if it exists in the layer's primary flow
|
||||
pub fn upstream_node_id_from_protonode(&self, protonode_identifier: &'static str) -> Option<NodeId> {
|
||||
pub fn upstream_node_id_from_protonode(&self, protonode_identifier: ProtoNodeIdentifier) -> Option<NodeId> {
|
||||
self.horizontal_layer_flow()
|
||||
// Take until a different layer is reached
|
||||
.take_while(|&node_id| node_id == self.layer_node || !self.network_interface.is_layer(&node_id, &[]))
|
||||
.find(move |node_id| {
|
||||
.find(|node_id| {
|
||||
self.network_interface
|
||||
.implementation(node_id, &[])
|
||||
.is_some_and(move |implementation| *implementation == graph_craft::document::DocumentNodeImplementation::proto(protonode_identifier))
|
||||
.is_some_and(|implementation| *implementation == graph_craft::document::DocumentNodeImplementation::ProtoNode(protonode_identifier.clone()))
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -1,26 +1,184 @@
|
||||
//! Handler for the pivot overlay visible on the selected layer(s) whilst using the Select tool which controls the center of rotation/scale and origin of the layer.
|
||||
//! Handler for the pivot overlay visible on the selected layer(s) whilst using the Select tool which controls the center of rotation/scale.
|
||||
|
||||
use super::graph_modification_utils;
|
||||
use crate::consts::PIVOT_DIAMETER;
|
||||
use crate::messages::portfolio::document::overlays::utility_types::OverlayContext;
|
||||
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
||||
use crate::messages::prelude::*;
|
||||
use crate::messages::tool::common_functionality::graph_modification_utils;
|
||||
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;
|
||||
use std::collections::VecDeque;
|
||||
use graphene_std::{transform::ReferencePoint, vector::ManipulatorPointId};
|
||||
use std::fmt;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub fn pin_pivot_widget(active: bool, enabled: bool, source: PivotToolSource) -> WidgetHolder {
|
||||
IconButton::new(if active { "PinActive" } else { "PinInactive" }, 24)
|
||||
.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(),
|
||||
})
|
||||
.widget_holder()
|
||||
}
|
||||
|
||||
pub fn pivot_reference_point_widget(disabled: bool, reference_point: ReferencePoint, source: PivotToolSource) -> WidgetHolder {
|
||||
ReferencePointInput::new(reference_point)
|
||||
.tooltip("Custom Pivot Reference Point\n\nPlaces the pivot at a corner, edge, or center of the selection bounds, unless it is dragged elsewhere.")
|
||||
.disabled(disabled)
|
||||
.on_update(move |pivot_input: &ReferencePointInput| match source {
|
||||
PivotToolSource::Select => SelectToolMessage::SetPivot { position: pivot_input.value }.into(),
|
||||
PivotToolSource::Path => PathToolMessage::SetPivot { position: pivot_input.value }.into(),
|
||||
})
|
||||
.widget_holder()
|
||||
}
|
||||
|
||||
pub fn pivot_gizmo_type_widget(state: PivotGizmoState, source: PivotToolSource) -> Vec<WidgetHolder> {
|
||||
let gizmo_type_entries = [PivotGizmoType::Pivot, PivotGizmoType::Average, PivotGizmoType::Active]
|
||||
.iter()
|
||||
.map(|gizmo_type| {
|
||||
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(),
|
||||
}
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
|
||||
vec![
|
||||
CheckboxInput::new(!state.disabled)
|
||||
.tooltip(
|
||||
"Pivot Gizmo\n\
|
||||
\n\
|
||||
Enabled: the chosen gizmo type is shown and used to control rotation and scaling.\n\
|
||||
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(),
|
||||
})
|
||||
.widget_holder(),
|
||||
Separator::new(SeparatorType::Related).widget_holder(),
|
||||
DropdownInput::new(vec![gizmo_type_entries])
|
||||
.selected_index(Some(match state.gizmo_type {
|
||||
PivotGizmoType::Pivot => 0,
|
||||
PivotGizmoType::Average => 1,
|
||||
PivotGizmoType::Active => 2,
|
||||
}))
|
||||
.tooltip(
|
||||
"Pivot Gizmo Type\n\
|
||||
\n\
|
||||
Selects which gizmo type is shown and used as the center of rotation/scaling transformations.\n\
|
||||
\n\
|
||||
Custom Pivot: rotates and scales relative to the selection bounds, or elsewhere if dragged.\n\
|
||||
Origin (Average Point): rotates and scales about the average point of all selected layer origins.\n\
|
||||
Origin (Active Object): rotates and scales about the origin of the most recently selected layer.",
|
||||
)
|
||||
.disabled(state.disabled)
|
||||
.widget_holder(),
|
||||
]
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone, Debug, Default, serde::Serialize, serde::Deserialize)]
|
||||
pub enum PivotToolSource {
|
||||
Path,
|
||||
#[default]
|
||||
Select,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone, Debug, Default, serde::Serialize, serde::Deserialize)]
|
||||
pub struct PivotGizmo {
|
||||
pub pivot: Pivot,
|
||||
pub state: PivotGizmoState,
|
||||
pub layer: Option<LayerNodeIdentifier>,
|
||||
pub point: Option<ManipulatorPointId>,
|
||||
}
|
||||
|
||||
impl PivotGizmo {
|
||||
pub fn position(&self, document: &DocumentMessageHandler) -> DVec2 {
|
||||
let network = &document.network_interface;
|
||||
(!self.state.disabled)
|
||||
.then_some({
|
||||
match self.state.gizmo_type {
|
||||
PivotGizmoType::Average => Some(network.selected_nodes().selected_visible_and_unlocked_layers_mean_average_origin(network)),
|
||||
PivotGizmoType::Pivot => self.pivot.pivot,
|
||||
PivotGizmoType::Active => self.layer.map(|layer| graph_modification_utils::get_viewport_origin(layer, network)),
|
||||
}
|
||||
})
|
||||
.flatten()
|
||||
.unwrap_or_else(|| self.pivot.transform_from_normalized.transform_point2(DVec2::splat(0.5)))
|
||||
}
|
||||
|
||||
pub fn recalculate_transform(&mut self, document: &DocumentMessageHandler) -> DAffine2 {
|
||||
self.pivot.recalculate_pivot(document);
|
||||
self.pivot.transform_from_normalized
|
||||
}
|
||||
|
||||
pub fn pin_active(&self) -> bool {
|
||||
self.pivot.pinned && self.state.is_pivot_type()
|
||||
}
|
||||
|
||||
pub fn pivot_disconnected(&self) -> bool {
|
||||
self.pivot.old_pivot_position == ReferencePoint::None
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, PartialEq, Eq, Clone, Copy, Debug, Hash, serde::Serialize, serde::Deserialize, specta::Type)]
|
||||
pub enum PivotGizmoType {
|
||||
// Pivot
|
||||
#[default]
|
||||
Pivot,
|
||||
// Origin
|
||||
Average,
|
||||
Active,
|
||||
// TODO: Add "Individual"
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Clone, Copy, Default, Debug, Hash, serde::Serialize, serde::Deserialize, specta::Type)]
|
||||
pub struct PivotGizmoState {
|
||||
pub disabled: bool,
|
||||
pub gizmo_type: PivotGizmoType,
|
||||
}
|
||||
|
||||
impl PivotGizmoState {
|
||||
pub fn is_pivot_type(&self) -> bool {
|
||||
self.gizmo_type == PivotGizmoType::Pivot || self.disabled
|
||||
}
|
||||
|
||||
pub fn is_pivot(&self) -> bool {
|
||||
self.gizmo_type == PivotGizmoType::Pivot && !self.disabled
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for PivotGizmoType {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
PivotGizmoType::Pivot => write!(f, "Custom Pivot"),
|
||||
PivotGizmoType::Average => write!(f, "Origin (Average Point)"),
|
||||
PivotGizmoType::Active => write!(f, "Origin (Active Object)"),
|
||||
// TODO: Add "Origin (Individual)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
|
||||
pub struct Pivot {
|
||||
/// Pivot between (0,0) and (1,1)
|
||||
normalized_pivot: DVec2,
|
||||
/// Transform to get from normalized pivot to viewspace
|
||||
transform_from_normalized: DAffine2,
|
||||
/// The viewspace pivot position (if applicable)
|
||||
pivot: Option<DVec2>,
|
||||
pub transform_from_normalized: DAffine2,
|
||||
/// The viewspace pivot position
|
||||
pub pivot: Option<DVec2>,
|
||||
/// The old pivot position in the GUI, used to reduce refreshes of the document bar
|
||||
old_pivot_position: ReferencePoint,
|
||||
pub old_pivot_position: ReferencePoint,
|
||||
/// The last ReferencePoint which wasn't none
|
||||
pub last_non_none_reference_point: ReferencePoint,
|
||||
/// Used to enable and disable the pivot
|
||||
active: bool,
|
||||
pub pinned: bool,
|
||||
/// Had selected_visible_and_unlocked_layers
|
||||
pub empty: bool,
|
||||
}
|
||||
|
||||
impl Default for Pivot {
|
||||
@@ -30,84 +188,62 @@ impl Default for Pivot {
|
||||
transform_from_normalized: Default::default(),
|
||||
pivot: Default::default(),
|
||||
old_pivot_position: ReferencePoint::Center,
|
||||
active: true,
|
||||
last_non_none_reference_point: ReferencePoint::Center,
|
||||
pinned: false,
|
||||
empty: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Pivot {
|
||||
/// Calculates the transform that gets from normalized pivot to viewspace.
|
||||
fn get_layer_pivot_transform(layer: LayerNodeIdentifier, document: &DocumentMessageHandler) -> DAffine2 {
|
||||
let [min, max] = document.metadata().nonzero_bounding_box(layer);
|
||||
|
||||
let bounds_transform = DAffine2::from_translation(min) * DAffine2::from_scale(max - min);
|
||||
let layer_transform = document.metadata().transform_to_viewport(layer);
|
||||
layer_transform * bounds_transform
|
||||
}
|
||||
|
||||
/// Recomputes the pivot position and transform.
|
||||
fn recalculate_pivot(&mut self, document: &DocumentMessageHandler) {
|
||||
if !self.active {
|
||||
pub fn recalculate_pivot(&mut self, document: &DocumentMessageHandler) {
|
||||
let selected = document.network_interface.selected_nodes();
|
||||
self.empty = !selected.has_selected_nodes();
|
||||
if !selected.has_selected_nodes() {
|
||||
return;
|
||||
}
|
||||
|
||||
let selected_nodes = document.network_interface.selected_nodes();
|
||||
let mut layers = selected_nodes.selected_visible_and_unlocked_layers(&document.network_interface);
|
||||
let Some(first) = layers.next() else {
|
||||
// If no layers are selected then we revert things back to default
|
||||
let transform = selected
|
||||
.selected_visible_and_unlocked_layers(&document.network_interface)
|
||||
.find(|layer| !document.network_interface.is_artboard(&layer.to_node(), &[]))
|
||||
.map(|layer| document.metadata().transform_to_viewport_with_first_transform_node_if_group(layer, &document.network_interface))
|
||||
.unwrap_or_default();
|
||||
|
||||
let bounds = document
|
||||
.network_interface
|
||||
.selected_nodes()
|
||||
.selected_visible_and_unlocked_layers(&document.network_interface)
|
||||
.filter_map(|layer| {
|
||||
document
|
||||
.metadata()
|
||||
.bounding_box_with_transform(layer, transform.inverse() * document.metadata().transform_to_viewport(layer))
|
||||
})
|
||||
.reduce(graphene_std::renderer::Quad::combine_bounds);
|
||||
|
||||
let [min, max] = bounds.unwrap_or([DVec2::ZERO, DVec2::ONE]);
|
||||
self.transform_from_normalized = transform * DAffine2::from_translation(min) * DAffine2::from_scale(max - min);
|
||||
|
||||
if self.old_pivot_position != ReferencePoint::None {
|
||||
self.pivot = Some(self.transform_from_normalized.transform_point2(self.normalized_pivot));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn recalculate_pivot_for_layer(&mut self, document: &DocumentMessageHandler, bounds: Option<[DVec2; 2]>) {
|
||||
let selected = document.network_interface.selected_nodes();
|
||||
if !selected.has_selected_nodes() {
|
||||
self.normalized_pivot = DVec2::splat(0.5);
|
||||
self.pivot = None;
|
||||
return;
|
||||
};
|
||||
|
||||
// Add one because the first item is consumed above.
|
||||
let selected_layers_count = layers.count() + 1;
|
||||
|
||||
// If just one layer is selected we can use its inner transform (as it accounts for rotation)
|
||||
if selected_layers_count == 1 {
|
||||
let normalized_pivot = graph_modification_utils::get_pivot(first, &document.network_interface).unwrap_or(DVec2::splat(0.5));
|
||||
self.normalized_pivot = normalized_pivot;
|
||||
self.transform_from_normalized = Self::get_layer_pivot_transform(first, document);
|
||||
self.pivot = Some(self.transform_from_normalized.transform_point2(normalized_pivot));
|
||||
} else {
|
||||
// If more than one layer is selected we use the AABB with the mean of the pivots
|
||||
let xy_summation = document
|
||||
.network_interface
|
||||
.selected_nodes()
|
||||
.selected_visible_and_unlocked_layers(&document.network_interface)
|
||||
.map(|layer| graph_modification_utils::get_viewport_pivot(layer, &document.network_interface))
|
||||
.reduce(|a, b| a + b)
|
||||
.unwrap_or_default();
|
||||
|
||||
let pivot = xy_summation / selected_layers_count as f64;
|
||||
self.pivot = Some(pivot);
|
||||
let [min, max] = document.selected_visible_and_unlock_layers_bounding_box_viewport().unwrap_or([DVec2::ZERO, DVec2::ONE]);
|
||||
self.normalized_pivot = (pivot - min) / (max - min);
|
||||
|
||||
self.transform_from_normalized = DAffine2::from_translation(min) * DAffine2::from_scale(max - min);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_pivot(&mut self, document: &DocumentMessageHandler, overlay_context: &mut OverlayContext, draw_data: Option<(f64,)>) {
|
||||
if !overlay_context.visibility_settings.pivot() {
|
||||
self.active = false;
|
||||
return;
|
||||
} else {
|
||||
self.active = true;
|
||||
}
|
||||
|
||||
self.recalculate_pivot(document);
|
||||
if let (Some(pivot), Some(data)) = (self.pivot, draw_data) {
|
||||
overlay_context.pivot(pivot, data.0);
|
||||
}
|
||||
let [min, max] = bounds.unwrap_or([DVec2::ZERO, DVec2::ONE]);
|
||||
self.transform_from_normalized = DAffine2::from_translation(min) * DAffine2::from_scale(max - min);
|
||||
self.pivot = Some(self.transform_from_normalized.transform_point2(self.normalized_pivot));
|
||||
}
|
||||
|
||||
/// Answers if the pivot widget has changed (so we should refresh the tool bar at the top of the canvas).
|
||||
pub fn should_refresh_pivot_position(&mut self) -> bool {
|
||||
if !self.active {
|
||||
return false;
|
||||
}
|
||||
|
||||
let new = self.to_pivot_position();
|
||||
let should_refresh = new != self.old_pivot_position;
|
||||
self.old_pivot_position = new;
|
||||
@@ -118,37 +254,24 @@ impl Pivot {
|
||||
self.normalized_pivot.into()
|
||||
}
|
||||
|
||||
/// Sets the viewport position of the pivot for all selected layers.
|
||||
pub fn set_viewport_position(&self, position: DVec2, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) {
|
||||
if !self.active {
|
||||
/// Sets the viewport position of the pivot.
|
||||
pub fn set_viewport_position(&mut self, position: DVec2) {
|
||||
if self.transform_from_normalized.matrix2.determinant().abs() <= f64::EPSILON {
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
for layer in document.network_interface.selected_nodes().selected_visible_and_unlocked_layers(&document.network_interface) {
|
||||
let transform = Self::get_layer_pivot_transform(layer, document);
|
||||
// Only update the pivot when computed position is finite.
|
||||
if transform.matrix2.determinant().abs() <= f64::EPSILON {
|
||||
return;
|
||||
};
|
||||
let pivot = transform.inverse().transform_point2(position);
|
||||
responses.add(GraphOperationMessage::TransformSetPivot { layer, pivot });
|
||||
}
|
||||
self.normalized_pivot = self.transform_from_normalized.inverse().transform_point2(position);
|
||||
self.pivot = Some(position);
|
||||
}
|
||||
|
||||
/// Set the pivot using the normalized transform that is set above.
|
||||
pub fn set_normalized_position(&self, position: DVec2, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) {
|
||||
if !self.active {
|
||||
return;
|
||||
}
|
||||
|
||||
self.set_viewport_position(self.transform_from_normalized.transform_point2(position), document, responses);
|
||||
/// Set the pivot using a normalized position.
|
||||
pub fn set_normalized_position(&mut self, position: DVec2) {
|
||||
self.normalized_pivot = position;
|
||||
self.pivot = Some(self.transform_from_normalized.transform_point2(position));
|
||||
}
|
||||
|
||||
/// Answers if the pointer is currently positioned over the pivot.
|
||||
pub fn is_over(&self, mouse: DVec2) -> bool {
|
||||
if !self.active {
|
||||
return false;
|
||||
}
|
||||
self.pivot.filter(|&pivot| mouse.distance_squared(pivot) < (PIVOT_DIAMETER / 2.).powi(2)).is_some()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -212,15 +212,15 @@ impl ClosestSegment {
|
||||
self.bezier_point_to_viewport
|
||||
}
|
||||
|
||||
pub fn closest_point(&self, document_metadata: &DocumentMetadata) -> DVec2 {
|
||||
let transform = document_metadata.transform_to_viewport(self.layer);
|
||||
pub fn closest_point(&self, document_metadata: &DocumentMetadata, network_interface: &NodeNetworkInterface) -> DVec2 {
|
||||
let transform = document_metadata.transform_to_viewport_if_feeds(self.layer, network_interface);
|
||||
let bezier_point = self.bezier.evaluate(TValue::Parametric(self.t));
|
||||
transform.transform_point2(bezier_point)
|
||||
}
|
||||
|
||||
/// Updates this [`ClosestSegment`] with the viewport-space location of the closest point on the segment to the given mouse position.
|
||||
pub fn update_closest_point(&mut self, document_metadata: &DocumentMetadata, mouse_position: DVec2) {
|
||||
let transform = document_metadata.transform_to_viewport(self.layer);
|
||||
pub fn update_closest_point(&mut self, document_metadata: &DocumentMetadata, network_interface: &NodeNetworkInterface, mouse_position: DVec2) {
|
||||
let transform = document_metadata.transform_to_viewport_if_feeds(self.layer, network_interface);
|
||||
let layer_mouse_pos = transform.inverse().transform_point2(mouse_position);
|
||||
|
||||
let t = self.bezier.project(layer_mouse_pos).clamp(0., 1.);
|
||||
@@ -239,9 +239,9 @@ impl ClosestSegment {
|
||||
tolerance.powi(2) < self.distance_squared(mouse_position)
|
||||
}
|
||||
|
||||
pub fn handle_positions(&self, document_metadata: &DocumentMetadata) -> (Option<DVec2>, Option<DVec2>) {
|
||||
pub fn handle_positions(&self, document_metadata: &DocumentMetadata, network_interface: &NodeNetworkInterface) -> (Option<DVec2>, Option<DVec2>) {
|
||||
// Transform to viewport space
|
||||
let transform = document_metadata.transform_to_viewport(self.layer);
|
||||
let transform = document_metadata.transform_to_viewport_if_feeds(self.layer, network_interface);
|
||||
|
||||
// Split the Bezier at the parameter `t`
|
||||
let [first, second] = self.bezier.split(TValue::Parametric(self.t));
|
||||
@@ -307,7 +307,7 @@ impl ClosestSegment {
|
||||
}
|
||||
|
||||
pub fn calculate_perp(&self, document: &DocumentMessageHandler) -> DVec2 {
|
||||
let tangent = if let (Some(handle1), Some(handle2)) = self.handle_positions(document.metadata()) {
|
||||
let tangent = if let (Some(handle1), Some(handle2)) = self.handle_positions(document.metadata(), &document.network_interface) {
|
||||
(handle1 - handle2).try_normalize()
|
||||
} else {
|
||||
let [first_point, last_point] = self.points();
|
||||
@@ -339,7 +339,7 @@ impl ClosestSegment {
|
||||
break_colinear_molding: bool,
|
||||
temporary_adjacent_handles_while_molding: Option<[Option<HandleId>; 2]>,
|
||||
) -> Option<[Option<HandleId>; 2]> {
|
||||
let transform = document.metadata().transform_to_viewport(self.layer);
|
||||
let transform = document.metadata().transform_to_viewport_if_feeds(self.layer, &document.network_interface);
|
||||
|
||||
let start = self.bezier.start;
|
||||
let end = self.bezier.end;
|
||||
@@ -507,7 +507,7 @@ impl ShapeState {
|
||||
continue;
|
||||
};
|
||||
|
||||
let to_document = document.metadata().transform_to_document(*layer);
|
||||
let to_document = document.metadata().transform_to_document_if_feeds(*layer, &document.network_interface);
|
||||
|
||||
for &selected in &state.selected_points {
|
||||
let source = match selected {
|
||||
@@ -564,7 +564,11 @@ impl ShapeState {
|
||||
let already_selected = selected_shape_state.is_point_selected(manipulator_point_id);
|
||||
|
||||
// Offset to snap the selected point to the cursor
|
||||
let offset = mouse_position - network_interface.document_metadata().transform_to_viewport(layer).transform_point2(point_position);
|
||||
let offset = mouse_position
|
||||
- network_interface
|
||||
.document_metadata()
|
||||
.transform_to_viewport_if_feeds(layer, network_interface)
|
||||
.transform_point2(point_position);
|
||||
|
||||
// This is selecting the manipulator only for now, next to generalize to points
|
||||
|
||||
@@ -621,7 +625,11 @@ impl ShapeState {
|
||||
let already_selected = selected_shape_state.is_point_selected(manipulator_point_id);
|
||||
|
||||
// Offset to snap the selected point to the cursor
|
||||
let offset = mouse_position - network_interface.document_metadata().transform_to_viewport(layer).transform_point2(point_position);
|
||||
let offset = mouse_position
|
||||
- network_interface
|
||||
.document_metadata()
|
||||
.transform_to_viewport_if_feeds(layer, network_interface)
|
||||
.transform_point2(point_position);
|
||||
|
||||
// Gather current selection information
|
||||
let points = self
|
||||
@@ -653,7 +661,7 @@ impl ShapeState {
|
||||
let Some(vector_data) = document.network_interface.compute_modified_vector(layer) else {
|
||||
return;
|
||||
};
|
||||
let to_viewport = document.metadata().transform_to_viewport(layer);
|
||||
let to_viewport = document.metadata().transform_to_viewport_if_feeds(layer, &document.network_interface);
|
||||
let layer_mouse = to_viewport.inverse().transform_point2(mouse);
|
||||
let state = self.selected_shape_state.entry(layer).or_default();
|
||||
|
||||
@@ -875,7 +883,7 @@ impl ShapeState {
|
||||
}
|
||||
|
||||
let vector_data = network_interface.compute_modified_vector(layer)?;
|
||||
let transform = network_interface.document_metadata().transform_to_document(layer).inverse();
|
||||
let transform = network_interface.document_metadata().transform_to_document_if_feeds(layer, network_interface).inverse();
|
||||
let position = transform.transform_point2(new_position);
|
||||
let current_position = point.get_position(&vector_data)?;
|
||||
let delta = position - current_position;
|
||||
@@ -1026,7 +1034,7 @@ impl ShapeState {
|
||||
let Some(vector_data) = document.network_interface.compute_modified_vector(layer) else {
|
||||
continue;
|
||||
};
|
||||
let transform = document.metadata().transform_to_document(layer);
|
||||
let transform = document.metadata().transform_to_document_if_feeds(layer, &document.network_interface);
|
||||
|
||||
for &point in layer_state.selected_points.iter() {
|
||||
let Some(handles) = point.get_handle_pair(&vector_data) else { continue };
|
||||
@@ -1071,7 +1079,7 @@ impl ShapeState {
|
||||
|
||||
let mut normalized = handle_directions[0].and_then(|a| handle_directions[1].and_then(|b| (a - b).try_normalize()));
|
||||
|
||||
if normalized.is_none() {
|
||||
if normalized.is_none() || handle_directions.iter().any(|&d| d.is_some_and(|d| d.length_squared() < f64::EPSILON * 1e5)) {
|
||||
handle_directions = anchor_positions.map(|relative_anchor| relative_anchor.map(|relative_anchor| (relative_anchor - anchor) / 3.));
|
||||
normalized = handle_directions[0].and_then(|a| handle_directions[1].and_then(|b| (a - b).try_normalize()))
|
||||
}
|
||||
@@ -1116,8 +1124,8 @@ impl ShapeState {
|
||||
|
||||
let opposing_handles = handle_lengths.as_ref().and_then(|handle_lengths| handle_lengths.get(&layer));
|
||||
|
||||
let transform_to_viewport_space = document.metadata().transform_to_viewport(layer);
|
||||
let transform_to_document_space = document.metadata().transform_to_document(layer);
|
||||
let transform_to_viewport_space = document.metadata().transform_to_viewport_if_feeds(layer, &document.network_interface);
|
||||
let transform_to_document_space = document.metadata().transform_to_document_if_feeds(layer, &document.network_interface);
|
||||
let delta_transform = if in_viewport_space {
|
||||
transform_to_viewport_space
|
||||
} else {
|
||||
@@ -1210,7 +1218,7 @@ impl ShapeState {
|
||||
.iter()
|
||||
.filter_map(|(&layer, state)| {
|
||||
let vector_data = document.network_interface.compute_modified_vector(layer)?;
|
||||
let transform = document.metadata().transform_to_document(layer);
|
||||
let transform = document.metadata().transform_to_document_if_feeds(layer, &document.network_interface);
|
||||
let opposing_handle_lengths = vector_data
|
||||
.colinear_manipulators
|
||||
.iter()
|
||||
@@ -1575,7 +1583,7 @@ impl ShapeState {
|
||||
let mut manipulator_point = None;
|
||||
|
||||
let vector_data = network_interface.compute_modified_vector(layer)?;
|
||||
let viewspace = network_interface.document_metadata().transform_to_viewport(layer);
|
||||
let viewspace = network_interface.document_metadata().transform_to_viewport_if_feeds(layer, network_interface);
|
||||
|
||||
// Handles
|
||||
for (segment_id, bezier, _, _) in vector_data.segment_bezier_iter() {
|
||||
@@ -1611,7 +1619,7 @@ impl ShapeState {
|
||||
|
||||
/// Find the `t` value along the path segment we have clicked upon, together with that segment ID.
|
||||
fn closest_segment(&self, network_interface: &NodeNetworkInterface, layer: LayerNodeIdentifier, position: glam::DVec2, tolerance: f64) -> Option<ClosestSegment> {
|
||||
let transform = network_interface.document_metadata().transform_to_viewport(layer);
|
||||
let transform = network_interface.document_metadata().transform_to_viewport_if_feeds(layer, network_interface);
|
||||
let layer_pos = transform.inverse().transform_point2(position);
|
||||
|
||||
let tolerance = tolerance + 0.5;
|
||||
@@ -1785,7 +1793,7 @@ impl ShapeState {
|
||||
pub fn flip_smooth_sharp(&self, network_interface: &NodeNetworkInterface, target: glam::DVec2, tolerance: f64, responses: &mut VecDeque<Message>) -> bool {
|
||||
let mut process_layer = |layer| {
|
||||
let vector_data = network_interface.compute_modified_vector(layer)?;
|
||||
let transform_to_screenspace = network_interface.document_metadata().transform_to_viewport(layer);
|
||||
let transform_to_screenspace = network_interface.document_metadata().transform_to_viewport_if_feeds(layer, network_interface);
|
||||
|
||||
let mut result = None;
|
||||
let mut closest_distance_squared = tolerance * tolerance;
|
||||
@@ -1889,7 +1897,7 @@ impl ShapeState {
|
||||
|
||||
let vector_data = network_interface.compute_modified_vector(layer);
|
||||
let Some(vector_data) = vector_data else { continue };
|
||||
let transform = network_interface.document_metadata().transform_to_viewport(layer);
|
||||
let transform = network_interface.document_metadata().transform_to_viewport_if_feeds(layer, network_interface);
|
||||
|
||||
assert_eq!(vector_data.segment_domain.ids().len(), vector_data.start_point().count());
|
||||
assert_eq!(vector_data.segment_domain.ids().len(), vector_data.end_point().count());
|
||||
|
||||
@@ -77,7 +77,7 @@ mod test_ellipse {
|
||||
layers
|
||||
.filter_map(|layer| {
|
||||
let node_graph_layer = NodeGraphLayer::new(layer, &document.network_interface);
|
||||
let ellipse_node = node_graph_layer.upstream_node_id_from_protonode(ellipse::protonode_identifier())?;
|
||||
let ellipse_node = node_graph_layer.upstream_node_id_from_protonode(ellipse::IDENTIFIER)?;
|
||||
Some(ResolvedEllipse {
|
||||
radius_x: instrumented.grab_protonode_input::<ellipse::RadiusXInput>(&vec![ellipse_node], &editor.runtime).unwrap(),
|
||||
radius_y: instrumented.grab_protonode_input::<ellipse::RadiusYInput>(&vec![ellipse_node], &editor.runtime).unwrap(),
|
||||
|
||||
@@ -393,6 +393,7 @@ pub fn transforming_transform_cage(
|
||||
input: &InputPreprocessorMessageHandler,
|
||||
responses: &mut VecDeque<Message>,
|
||||
layers_dragging: &mut Vec<LayerNodeIdentifier>,
|
||||
center_of_transformation: Option<DVec2>,
|
||||
) -> (bool, bool, bool) {
|
||||
let dragging_bounds = bounding_box_manager.as_mut().and_then(|bounding_box| {
|
||||
let edges = bounding_box.check_selected_edges(input.mouse.position);
|
||||
@@ -429,17 +430,12 @@ pub fn transforming_transform_cage(
|
||||
}
|
||||
});
|
||||
|
||||
let mut selected = Selected::new(
|
||||
&mut bounds.original_transforms,
|
||||
&mut bounds.center_of_transformation,
|
||||
layers_dragging,
|
||||
responses,
|
||||
&document.network_interface,
|
||||
None,
|
||||
&ToolType::Select,
|
||||
None,
|
||||
);
|
||||
bounds.center_of_transformation = selected.mean_average_of_pivots();
|
||||
bounds.center_of_transformation = center_of_transformation.unwrap_or_else(|| {
|
||||
document
|
||||
.network_interface
|
||||
.selected_nodes()
|
||||
.selected_visible_and_unlocked_layers_mean_average_origin(&document.network_interface)
|
||||
});
|
||||
|
||||
// Check if we're hovering over a skew triangle
|
||||
let edges = bounds.check_selected_edges(input.mouse.position);
|
||||
@@ -469,18 +465,12 @@ pub fn transforming_transform_cage(
|
||||
}
|
||||
});
|
||||
|
||||
let mut selected = Selected::new(
|
||||
&mut bounds.original_transforms,
|
||||
&mut bounds.center_of_transformation,
|
||||
&selected,
|
||||
responses,
|
||||
&document.network_interface,
|
||||
None,
|
||||
&ToolType::Select,
|
||||
None,
|
||||
);
|
||||
|
||||
bounds.center_of_transformation = selected.mean_average_of_pivots();
|
||||
bounds.center_of_transformation = center_of_transformation.unwrap_or_else(|| {
|
||||
document
|
||||
.network_interface
|
||||
.selected_nodes()
|
||||
.selected_visible_and_unlocked_layers_mean_average_origin(&document.network_interface)
|
||||
});
|
||||
}
|
||||
|
||||
*layers_dragging = selected;
|
||||
|
||||
@@ -12,6 +12,7 @@ use graphene_std::raster::color::Color;
|
||||
|
||||
const ARTBOARD_OVERLAY_PROVIDER: OverlayProvider = |context| DocumentMessage::DrawArtboardOverlays(context).into();
|
||||
|
||||
#[derive(ExtractField)]
|
||||
pub struct ToolMessageData<'a> {
|
||||
pub document_id: DocumentId,
|
||||
pub document: &'a mut DocumentMessageHandler,
|
||||
@@ -21,7 +22,7 @@ pub struct ToolMessageData<'a> {
|
||||
pub preferences: &'a PreferencesMessageHandler,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
#[derive(Debug, Default, ExtractField)]
|
||||
pub struct ToolMessageHandler {
|
||||
pub tool_state: ToolFsmState,
|
||||
pub transform_layer_handler: TransformLayerMessageHandler,
|
||||
@@ -29,6 +30,7 @@ pub struct ToolMessageHandler {
|
||||
pub tool_is_active: bool,
|
||||
}
|
||||
|
||||
#[message_handler_data]
|
||||
impl MessageHandler<ToolMessage, ToolMessageData<'_>> for ToolMessageHandler {
|
||||
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, data: ToolMessageData) {
|
||||
let ToolMessageData {
|
||||
@@ -181,6 +183,11 @@ impl MessageHandler<ToolMessage, ToolMessageData<'_>> for ToolMessageHandler {
|
||||
send: Box::new(TransformLayerMessage::SelectionChanged.into()),
|
||||
});
|
||||
|
||||
responses.add(BroadcastMessage::SubscribeEvent {
|
||||
on: BroadcastEvent::SelectionChanged,
|
||||
send: Box::new(SelectToolMessage::SyncHistory.into()),
|
||||
});
|
||||
|
||||
self.tool_is_active = true;
|
||||
|
||||
let tool_data = &mut self.tool_state.tool_data;
|
||||
|
||||
@@ -13,7 +13,7 @@ use crate::messages::tool::common_functionality::transformation_cage::*;
|
||||
use graph_craft::document::NodeId;
|
||||
use graphene_std::renderer::Quad;
|
||||
|
||||
#[derive(Default)]
|
||||
#[derive(Default, ExtractField)]
|
||||
pub struct ArtboardTool {
|
||||
fsm_state: ArtboardToolFsmState,
|
||||
data: ArtboardToolData,
|
||||
@@ -48,6 +48,7 @@ impl ToolMetadata for ArtboardTool {
|
||||
}
|
||||
}
|
||||
|
||||
#[message_handler_data]
|
||||
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for ArtboardTool {
|
||||
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
|
||||
self.fsm_state.process_event(message, &mut self.data, tool_data, &(), responses, false);
|
||||
@@ -567,7 +568,7 @@ mod test_artboard {
|
||||
Ok(instrumented) => instrumented,
|
||||
Err(e) => panic!("Failed to evaluate graph: {}", e),
|
||||
};
|
||||
instrumented.grab_all_input::<graphene_std::append_artboard::ArtboardInput>(&editor.runtime).collect()
|
||||
instrumented.grab_all_input::<graphene_std::graphic_element::append_artboard::ArtboardInput>(&editor.runtime).collect()
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use super::tool_prelude::*;
|
||||
use crate::consts::DEFAULT_BRUSH_SIZE;
|
||||
use crate::messages::portfolio::document::graph_operation::transform_utils::{get_current_normalized_pivot, get_current_transform};
|
||||
use crate::messages::portfolio::document::graph_operation::transform_utils::get_current_transform;
|
||||
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::FlowType;
|
||||
@@ -20,7 +20,7 @@ pub enum DrawMode {
|
||||
Restore,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
#[derive(Default, ExtractField)]
|
||||
pub struct BrushTool {
|
||||
fsm_state: BrushToolFsmState,
|
||||
data: BrushToolData,
|
||||
@@ -185,6 +185,7 @@ impl LayoutHolder for BrushTool {
|
||||
}
|
||||
}
|
||||
|
||||
#[message_handler_data]
|
||||
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for BrushTool {
|
||||
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
|
||||
let ToolMessage::Brush(BrushToolMessage::UpdateOptions(action)) = message else {
|
||||
@@ -286,9 +287,7 @@ impl BrushToolData {
|
||||
}
|
||||
|
||||
if *reference == Some("Transform".to_string()) {
|
||||
let upstream = document.metadata().upstream_transform(node_id);
|
||||
let pivot = DAffine2::from_translation(upstream.transform_point2(get_current_normalized_pivot(&node.inputs)));
|
||||
self.transform = pivot * get_current_transform(&node.inputs) * pivot.inverse() * self.transform;
|
||||
self.transform = get_current_transform(&node.inputs) * self.transform;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use super::tool_prelude::*;
|
||||
use crate::messages::tool::utility_types::DocumentToolData;
|
||||
|
||||
#[derive(Default)]
|
||||
#[derive(Default, ExtractField)]
|
||||
pub struct EyedropperTool {
|
||||
fsm_state: EyedropperToolFsmState,
|
||||
data: EyedropperToolData,
|
||||
@@ -39,6 +39,7 @@ impl LayoutHolder for EyedropperTool {
|
||||
}
|
||||
}
|
||||
|
||||
#[message_handler_data]
|
||||
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for EyedropperTool {
|
||||
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
|
||||
self.fsm_state.process_event(message, &mut self.data, tool_data, &(), responses, true);
|
||||
|
||||
@@ -3,7 +3,7 @@ use crate::messages::portfolio::document::overlays::utility_types::OverlayContex
|
||||
use crate::messages::tool::common_functionality::graph_modification_utils::NodeGraphLayer;
|
||||
use graphene_std::vector::style::Fill;
|
||||
|
||||
#[derive(Default)]
|
||||
#[derive(Default, ExtractField)]
|
||||
pub struct FillTool {
|
||||
fsm_state: FillToolFsmState,
|
||||
}
|
||||
@@ -41,6 +41,7 @@ impl LayoutHolder for FillTool {
|
||||
}
|
||||
}
|
||||
|
||||
#[message_handler_data]
|
||||
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for FillTool {
|
||||
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
|
||||
self.fsm_state.process_event(message, &mut (), tool_data, &(), responses, true);
|
||||
|
||||
@@ -13,7 +13,7 @@ use graphene_std::Color;
|
||||
use graphene_std::vector::VectorModificationType;
|
||||
use graphene_std::vector::{PointId, SegmentId};
|
||||
|
||||
#[derive(Default)]
|
||||
#[derive(Default, ExtractField)]
|
||||
pub struct FreehandTool {
|
||||
fsm_state: FreehandToolFsmState,
|
||||
data: FreehandToolData,
|
||||
@@ -116,6 +116,7 @@ impl LayoutHolder for FreehandTool {
|
||||
}
|
||||
}
|
||||
|
||||
#[message_handler_data]
|
||||
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for FreehandTool {
|
||||
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
|
||||
let ToolMessage::Freehand(FreehandToolMessage::UpdateOptions(action)) = message else {
|
||||
|
||||
@@ -7,7 +7,7 @@ use crate::messages::tool::common_functionality::graph_modification_utils::{Node
|
||||
use crate::messages::tool::common_functionality::snapping::SnapManager;
|
||||
use graphene_std::vector::style::{Fill, Gradient, GradientType};
|
||||
|
||||
#[derive(Default)]
|
||||
#[derive(Default, ExtractField)]
|
||||
pub struct GradientTool {
|
||||
fsm_state: GradientToolFsmState,
|
||||
data: GradientToolData,
|
||||
@@ -53,6 +53,7 @@ impl ToolMetadata for GradientTool {
|
||||
}
|
||||
}
|
||||
|
||||
#[message_handler_data]
|
||||
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for GradientTool {
|
||||
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
|
||||
let ToolMessage::Gradient(GradientToolMessage::UpdateOptions(action)) = message else {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use super::tool_prelude::*;
|
||||
|
||||
#[derive(Default)]
|
||||
#[derive(Default, ExtractField)]
|
||||
pub struct NavigateTool {
|
||||
fsm_state: NavigateToolFsmState,
|
||||
tool_data: NavigateToolData,
|
||||
@@ -38,6 +38,7 @@ impl LayoutHolder for NavigateTool {
|
||||
}
|
||||
}
|
||||
|
||||
#[message_handler_data]
|
||||
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for NavigateTool {
|
||||
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
|
||||
self.fsm_state.process_event(message, &mut self.tool_data, tool_data, &(), responses, true);
|
||||
|
||||
@@ -11,6 +11,7 @@ use crate::messages::portfolio::document::utility_types::network_interface::Node
|
||||
use crate::messages::portfolio::document::utility_types::transformation::Axis;
|
||||
use crate::messages::preferences::SelectionMode;
|
||||
use crate::messages::tool::common_functionality::auto_panning::AutoPanning;
|
||||
use crate::messages::tool::common_functionality::pivot::{PivotGizmo, PivotGizmoType, PivotToolSource, pin_pivot_widget, pivot_gizmo_type_widget, pivot_reference_point_widget};
|
||||
use crate::messages::tool::common_functionality::shape_editor::{
|
||||
ClosestSegment, ManipulatorAngle, OpposingHandleLengths, SelectedLayerState, SelectedPointsInfo, SelectionChange, SelectionShape, SelectionShapeType, ShapeState,
|
||||
};
|
||||
@@ -18,11 +19,12 @@ use crate::messages::tool::common_functionality::snapping::{SnapCache, SnapCandi
|
||||
use crate::messages::tool::common_functionality::utility_functions::{calculate_segment_angle, find_two_param_best_approximate};
|
||||
use bezier_rs::{Bezier, BezierHandles, TValue};
|
||||
use graphene_std::renderer::Quad;
|
||||
use graphene_std::transform::ReferencePoint;
|
||||
use graphene_std::vector::{HandleExt, HandleId, NoHashBuilder, SegmentId, VectorData};
|
||||
use graphene_std::vector::{ManipulatorPointId, PointId, VectorModificationType};
|
||||
use std::vec;
|
||||
|
||||
#[derive(Default)]
|
||||
#[derive(Default, ExtractField)]
|
||||
pub struct PathTool {
|
||||
fsm_state: PathToolFsmState,
|
||||
tool_data: PathToolData,
|
||||
@@ -106,6 +108,9 @@ pub enum PathToolMessage {
|
||||
SelectedPointYChanged {
|
||||
new_y: f64,
|
||||
},
|
||||
SetPivot {
|
||||
position: ReferencePoint,
|
||||
},
|
||||
SwapSelectedHandles,
|
||||
UpdateOptions(PathOptionsUpdate),
|
||||
UpdateSelectedPointsStatus {
|
||||
@@ -141,6 +146,9 @@ pub enum PathOptionsUpdate {
|
||||
OverlayModeType(PathOverlayMode),
|
||||
PointEditingMode { enabled: bool },
|
||||
SegmentEditingMode { enabled: bool },
|
||||
PivotGizmoType(PivotGizmoType),
|
||||
TogglePivotGizmoType(bool),
|
||||
TogglePivotPinned,
|
||||
}
|
||||
|
||||
impl ToolMetadata for PathTool {
|
||||
@@ -255,6 +263,20 @@ impl LayoutHolder for PathTool {
|
||||
.selected_index(Some(self.options.path_overlay_mode as u32))
|
||||
.widget_holder();
|
||||
|
||||
let [_checkbox, _dropdown] = {
|
||||
let pivot_gizmo_type_widget = pivot_gizmo_type_widget(self.tool_data.pivot_gizmo.state, PivotToolSource::Path);
|
||||
[pivot_gizmo_type_widget[0].clone(), pivot_gizmo_type_widget[2].clone()]
|
||||
};
|
||||
|
||||
let has_something = !self.tool_data.saved_points_before_anchor_convert_smooth_sharp.is_empty();
|
||||
let _pivot_reference = pivot_reference_point_widget(
|
||||
has_something || !self.tool_data.pivot_gizmo.state.is_pivot(),
|
||||
self.tool_data.pivot_gizmo.pivot.to_pivot_position(),
|
||||
PivotToolSource::Path,
|
||||
);
|
||||
|
||||
let _pin_pivot = pin_pivot_widget(self.tool_data.pivot_gizmo.pin_active(), false, PivotToolSource::Path);
|
||||
|
||||
Layout::WidgetLayout(WidgetLayout::new(vec![LayoutGroup::Row {
|
||||
widgets: vec![
|
||||
x_location,
|
||||
@@ -268,13 +290,22 @@ impl LayoutHolder for PathTool {
|
||||
point_editing_mode,
|
||||
related_seperator.clone(),
|
||||
segment_editing_mode,
|
||||
unrelated_seperator,
|
||||
unrelated_seperator.clone(),
|
||||
path_overlay_mode_widget,
|
||||
unrelated_seperator.clone(),
|
||||
// checkbox.clone(),
|
||||
// related_seperator.clone(),
|
||||
// dropdown.clone(),
|
||||
// unrelated_seperator,
|
||||
// pivot_reference,
|
||||
// related_seperator.clone(),
|
||||
// pin_pivot,
|
||||
],
|
||||
}]))
|
||||
}
|
||||
}
|
||||
|
||||
#[message_handler_data]
|
||||
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for PathTool {
|
||||
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
|
||||
let updating_point = message == ToolMessage::Path(PathToolMessage::SelectedPointUpdated);
|
||||
@@ -293,6 +324,29 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for PathToo
|
||||
self.options.path_editing_mode.segment_editing_mode = enabled;
|
||||
responses.add(OverlaysMessage::Draw);
|
||||
}
|
||||
PathOptionsUpdate::PivotGizmoType(gizmo_type) => {
|
||||
if !self.tool_data.pivot_gizmo.state.disabled {
|
||||
self.tool_data.pivot_gizmo.state.gizmo_type = gizmo_type;
|
||||
responses.add(ToolMessage::UpdateHints);
|
||||
let pivot_gizmo = self.tool_data.pivot_gizmo();
|
||||
responses.add(TransformLayerMessage::SetPivotGizmo { pivot_gizmo });
|
||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||
self.send_layout(responses, LayoutTarget::ToolOptions);
|
||||
}
|
||||
}
|
||||
PathOptionsUpdate::TogglePivotGizmoType(state) => {
|
||||
self.tool_data.pivot_gizmo.state.disabled = !state;
|
||||
responses.add(ToolMessage::UpdateHints);
|
||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||
self.send_layout(responses, LayoutTarget::ToolOptions);
|
||||
}
|
||||
|
||||
PathOptionsUpdate::TogglePivotPinned => {
|
||||
self.tool_data.pivot_gizmo.pivot.pinned = !self.tool_data.pivot_gizmo.pivot.pinned;
|
||||
responses.add(ToolMessage::UpdateHints);
|
||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||
self.send_layout(responses, LayoutTarget::ToolOptions);
|
||||
}
|
||||
},
|
||||
ToolMessage::Path(PathToolMessage::ClosePath) => {
|
||||
responses.add(DocumentMessage::AddTransaction);
|
||||
@@ -443,6 +497,8 @@ struct PathToolData {
|
||||
last_click_time: u64,
|
||||
dragging_state: DraggingState,
|
||||
angle: f64,
|
||||
pivot_gizmo: PivotGizmo,
|
||||
ordered_points: Vec<ManipulatorPointId>,
|
||||
opposite_handle_position: Option<DVec2>,
|
||||
last_clicked_point_was_selected: bool,
|
||||
last_clicked_segment_was_selected: bool,
|
||||
@@ -718,7 +774,7 @@ impl PathToolData {
|
||||
let Some(vector_data) = document.network_interface.compute_modified_vector(layer) else {
|
||||
continue;
|
||||
};
|
||||
let transform = document.metadata().transform_to_document(layer);
|
||||
let transform = document.metadata().transform_to_document_if_feeds(layer, &document.network_interface);
|
||||
|
||||
let mut layer_manipulators = HashSet::with_hasher(NoHashBuilder);
|
||||
for point in state.selected_points() {
|
||||
@@ -828,7 +884,7 @@ impl PathToolData {
|
||||
let selected_handle = selection.selected_points().next()?.as_handle()?;
|
||||
let handle_id = selected_handle.to_manipulator_point();
|
||||
|
||||
let layer_to_document = document.metadata().transform_to_document(*layer);
|
||||
let layer_to_document = document.metadata().transform_to_document_if_feeds(*layer, &document.network_interface);
|
||||
let vector_data = document.network_interface.compute_modified_vector(*layer)?;
|
||||
|
||||
let handle_position_local = selected_handle.to_manipulator_point().get_position(&vector_data)?;
|
||||
@@ -870,7 +926,7 @@ impl PathToolData {
|
||||
let anchor = handle_id.get_anchor(&vector_data);
|
||||
let (angle, anchor_position) = calculate_adjacent_anchor_tangent(handle_id, anchor, adjacent_anchor, &vector_data);
|
||||
|
||||
let layer_to_document = document.metadata().transform_to_document(*layer);
|
||||
let layer_to_document = document.metadata().transform_to_document_if_feeds(*layer, &document.network_interface);
|
||||
|
||||
self.adjacent_anchor_offset = handle_id
|
||||
.get_anchor_position(&vector_data)
|
||||
@@ -1017,7 +1073,7 @@ impl PathToolData {
|
||||
}
|
||||
// If already hovering on a segment, then recalculate its closest point
|
||||
else if let Some(closest_segment) = &mut self.segment {
|
||||
closest_segment.update_closest_point(document.metadata(), position);
|
||||
closest_segment.update_closest_point(document.metadata(), &document.network_interface, position);
|
||||
|
||||
if closest_segment.too_far(position, SEGMENT_INSERTION_DISTANCE) {
|
||||
self.segment = None;
|
||||
@@ -1084,7 +1140,7 @@ impl PathToolData {
|
||||
let layer = sliding_point_info.layer;
|
||||
|
||||
let Some(vector_data) = network_interface.compute_modified_vector(layer) else { return };
|
||||
let transform = network_interface.document_metadata().transform_to_viewport(layer);
|
||||
let transform = network_interface.document_metadata().transform_to_viewport_if_feeds(layer, network_interface);
|
||||
let layer_pos = transform.inverse().transform_point2(target_position);
|
||||
|
||||
let segments = sliding_point_info.connected_segments;
|
||||
@@ -1315,6 +1371,16 @@ impl PathToolData {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn pivot_gizmo(&self) -> PivotGizmo {
|
||||
self.pivot_gizmo.clone()
|
||||
}
|
||||
|
||||
fn sync_history(&mut self, points: &[ManipulatorPointId]) {
|
||||
self.ordered_points.retain(|layer| points.contains(layer));
|
||||
self.ordered_points.extend(points.iter().find(|&layer| !self.ordered_points.contains(layer)));
|
||||
self.pivot_gizmo.point = self.ordered_points.last().copied()
|
||||
}
|
||||
}
|
||||
|
||||
impl Fsm for PathToolFsmState {
|
||||
@@ -1327,6 +1393,10 @@ impl Fsm for PathToolFsmState {
|
||||
update_dynamic_hints(self, responses, shape_editor, document, tool_data, tool_options);
|
||||
|
||||
let ToolMessage::Path(event) = event else { return self };
|
||||
|
||||
// TODO(mTvare6): Remove once gizmos are implemented for path_tool
|
||||
tool_data.pivot_gizmo.state.disabled = true;
|
||||
|
||||
match (self, event) {
|
||||
(_, PathToolMessage::SelectionChanged) => {
|
||||
// Set the newly targeted layers to visible
|
||||
@@ -1343,6 +1413,9 @@ impl Fsm for PathToolFsmState {
|
||||
shape_editor.update_selected_anchors_status(display_anchors);
|
||||
shape_editor.update_selected_handles_status(display_handles);
|
||||
|
||||
let new_points = shape_editor.selected_points().copied().collect::<Vec<_>>();
|
||||
tool_data.sync_history(&new_points);
|
||||
|
||||
self
|
||||
}
|
||||
(_, PathToolMessage::Overlays(mut overlay_context)) => {
|
||||
@@ -1412,7 +1485,7 @@ impl Fsm for PathToolFsmState {
|
||||
|
||||
if let Some(closest_segment) = &tool_data.segment {
|
||||
if tool_options.path_editing_mode.segment_editing_mode {
|
||||
let transform = document.metadata().transform_to_viewport(closest_segment.layer());
|
||||
let transform = document.metadata().transform_to_viewport_if_feeds(closest_segment.layer(), &document.network_interface);
|
||||
|
||||
overlay_context.outline_overlay_bezier(closest_segment.bezier(), transform);
|
||||
|
||||
@@ -1430,7 +1503,7 @@ impl Fsm for PathToolFsmState {
|
||||
}
|
||||
} else {
|
||||
let perp = closest_segment.calculate_perp(document);
|
||||
let point = closest_segment.closest_point(document.metadata());
|
||||
let point = closest_segment.closest_point(document.metadata(), &document.network_interface);
|
||||
|
||||
// Draw an X on the segment
|
||||
if tool_data.delete_segment_pressed {
|
||||
@@ -1710,14 +1783,8 @@ impl Fsm for PathToolFsmState {
|
||||
}
|
||||
(PathToolFsmState::Ready, PathToolMessage::PointerMove { delete_segment, .. }) => {
|
||||
tool_data.delete_segment_pressed = input.keyboard.get(delete_segment as usize);
|
||||
|
||||
if !tool_data.saved_points_before_anchor_convert_smooth_sharp.is_empty() {
|
||||
tool_data.saved_points_before_anchor_convert_smooth_sharp.clear();
|
||||
}
|
||||
|
||||
if tool_data.adjacent_anchor_offset.is_some() {
|
||||
tool_data.adjacent_anchor_offset = None;
|
||||
}
|
||||
tool_data.saved_points_before_anchor_convert_smooth_sharp.clear();
|
||||
tool_data.adjacent_anchor_offset = None;
|
||||
tool_data.stored_selection = None;
|
||||
|
||||
responses.add(OverlaysMessage::Draw);
|
||||
@@ -2208,6 +2275,18 @@ impl Fsm for PathToolFsmState {
|
||||
responses.add(DocumentMessage::EndTransaction);
|
||||
PathToolFsmState::Ready
|
||||
}
|
||||
(_, PathToolMessage::SetPivot { position }) => {
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
|
||||
tool_data.pivot_gizmo.pivot.last_non_none_reference_point = position;
|
||||
let position: Option<DVec2> = position.into();
|
||||
tool_data.pivot_gizmo.pivot.set_normalized_position(position.unwrap());
|
||||
let pivot_gizmo = tool_data.pivot_gizmo();
|
||||
responses.add(TransformLayerMessage::SetPivotGizmo { pivot_gizmo });
|
||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||
|
||||
self
|
||||
}
|
||||
(_, _) => PathToolFsmState::Ready,
|
||||
}
|
||||
}
|
||||
@@ -2280,7 +2359,10 @@ fn get_selection_status(network_interface: &NodeNetworkInterface, shape_state: &
|
||||
return SelectionStatus::None;
|
||||
};
|
||||
|
||||
let coordinates = network_interface.document_metadata().transform_to_document(layer).transform_point2(local_position);
|
||||
let coordinates = network_interface
|
||||
.document_metadata()
|
||||
.transform_to_document_if_feeds(layer, network_interface)
|
||||
.transform_point2(local_position);
|
||||
let manipulator_angle = if vector_data.colinear(point) { ManipulatorAngle::Colinear } else { ManipulatorAngle::Free };
|
||||
|
||||
return SelectionStatus::One(SingleSelectedPoint {
|
||||
|
||||
@@ -17,7 +17,7 @@ use graphene_std::Color;
|
||||
use graphene_std::vector::{HandleId, ManipulatorPointId, NoHashBuilder, SegmentId, StrokeId, VectorData};
|
||||
use graphene_std::vector::{PointId, VectorModificationType};
|
||||
|
||||
#[derive(Default)]
|
||||
#[derive(Default, ExtractField)]
|
||||
pub struct PenTool {
|
||||
fsm_state: PenToolFsmState,
|
||||
tool_data: PenToolData,
|
||||
@@ -186,6 +186,7 @@ impl LayoutHolder for PenTool {
|
||||
}
|
||||
}
|
||||
|
||||
#[message_handler_data]
|
||||
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for PenTool {
|
||||
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
|
||||
let ToolMessage::Pen(PenToolMessage::UpdateOptions(action)) = message else {
|
||||
|
||||
@@ -12,9 +12,10 @@ use crate::messages::portfolio::document::utility_types::nodes::SelectedNodes;
|
||||
use crate::messages::preferences::SelectionMode;
|
||||
use crate::messages::tool::common_functionality::auto_panning::AutoPanning;
|
||||
use crate::messages::tool::common_functionality::compass_rose::{Axis, CompassRose};
|
||||
use crate::messages::tool::common_functionality::graph_modification_utils;
|
||||
use crate::messages::tool::common_functionality::graph_modification_utils::is_layer_fed_by_node_of_name;
|
||||
use crate::messages::tool::common_functionality::measure;
|
||||
use crate::messages::tool::common_functionality::pivot::Pivot;
|
||||
use crate::messages::tool::common_functionality::pivot::{PivotGizmo, PivotGizmoType, PivotToolSource, pin_pivot_widget, pivot_gizmo_type_widget, pivot_reference_point_widget};
|
||||
use crate::messages::tool::common_functionality::shape_editor::SelectionShapeType;
|
||||
use crate::messages::tool::common_functionality::snapping::{self, SnapCandidatePoint, SnapData, SnapManager};
|
||||
use crate::messages::tool::common_functionality::transformation_cage::*;
|
||||
@@ -28,7 +29,7 @@ use graphene_std::renderer::Rect;
|
||||
use graphene_std::transform::ReferencePoint;
|
||||
use std::fmt;
|
||||
|
||||
#[derive(Default)]
|
||||
#[derive(Default, ExtractField)]
|
||||
pub struct SelectTool {
|
||||
fsm_state: SelectToolFsmState,
|
||||
tool_data: SelectToolData,
|
||||
@@ -43,6 +44,9 @@ pub struct SelectOptions {
|
||||
#[derive(PartialEq, Eq, Clone, Debug, Hash, serde::Serialize, serde::Deserialize, specta::Type)]
|
||||
pub enum SelectOptionsUpdate {
|
||||
NestedSelectionBehavior(NestedSelectionBehavior),
|
||||
PivotGizmoType(PivotGizmoType),
|
||||
TogglePivotGizmoType(bool),
|
||||
TogglePivotPinned,
|
||||
}
|
||||
|
||||
#[derive(Default, PartialEq, Eq, Clone, Copy, Debug, Hash, serde::Serialize, serde::Deserialize, specta::Type)]
|
||||
@@ -95,6 +99,14 @@ pub enum SelectToolMessage {
|
||||
SetPivot {
|
||||
position: ReferencePoint,
|
||||
},
|
||||
SyncHistory,
|
||||
ShiftSelectedNodes {
|
||||
offset: DVec2,
|
||||
},
|
||||
PivotShift {
|
||||
offset: Option<DVec2>,
|
||||
flush: bool,
|
||||
},
|
||||
}
|
||||
|
||||
impl ToolMetadata for SelectTool {
|
||||
@@ -122,14 +134,12 @@ impl SelectTool {
|
||||
|
||||
DropdownInput::new(vec![layer_selection_behavior_entries])
|
||||
.selected_index(Some((self.tool_data.nested_selection_behavior == NestedSelectionBehavior::Deepest) as u32))
|
||||
.tooltip("Choose if clicking nested layers directly selects the deepest, or selects the shallowest and deepens by double clicking")
|
||||
.widget_holder()
|
||||
}
|
||||
|
||||
fn pivot_reference_point_widget(&self, disabled: bool) -> WidgetHolder {
|
||||
ReferencePointInput::new(self.tool_data.pivot.to_pivot_position())
|
||||
.on_update(|pivot_input: &ReferencePointInput| SelectToolMessage::SetPivot { position: pivot_input.value }.into())
|
||||
.disabled(disabled)
|
||||
.tooltip(
|
||||
"Selection Mode\n\
|
||||
\n\
|
||||
Shallow Select: clicks initially select the least-nested layers and double clicks drill deeper into the folder hierarchy.\n\
|
||||
Deep Select: clicks directly select the most-nested layers in the folder hierarchy.",
|
||||
)
|
||||
.widget_holder()
|
||||
}
|
||||
|
||||
@@ -178,7 +188,7 @@ impl SelectTool {
|
||||
|
||||
fn boolean_widgets(&self, selected_count: usize) -> impl Iterator<Item = WidgetHolder> + use<> {
|
||||
let list = <BooleanOperation as graphene_std::registry::ChoiceTypeStatic>::list();
|
||||
list.into_iter().map(|i| i.into_iter()).flatten().map(move |(operation, info)| {
|
||||
list.iter().flat_map(|i| i.iter()).map(move |(operation, info)| {
|
||||
let mut tooltip = info.label.to_string();
|
||||
if let Some(doc) = info.docstring.as_deref() {
|
||||
tooltip.push_str("\n\n");
|
||||
@@ -203,9 +213,29 @@ impl LayoutHolder for SelectTool {
|
||||
// Select mode (Deep/Shallow)
|
||||
widgets.push(self.deep_selection_widget());
|
||||
|
||||
// Pivot
|
||||
// Pivot gizmo type (checkbox + dropdown for pivot/origin)
|
||||
widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder());
|
||||
widgets.push(self.pivot_reference_point_widget(self.tool_data.selected_layers_count == 0));
|
||||
widgets.extend(pivot_gizmo_type_widget(self.tool_data.pivot_gizmo.state, PivotToolSource::Select));
|
||||
|
||||
if self.tool_data.pivot_gizmo.state.is_pivot_type() {
|
||||
// Nine-position reference point widget
|
||||
widgets.push(Separator::new(SeparatorType::Related).widget_holder());
|
||||
widgets.push(pivot_reference_point_widget(
|
||||
self.tool_data.selected_layers_count == 0 || !self.tool_data.pivot_gizmo.state.is_pivot(),
|
||||
self.tool_data.pivot_gizmo.pivot.to_pivot_position(),
|
||||
PivotToolSource::Select,
|
||||
));
|
||||
|
||||
// Pivot pin button
|
||||
widgets.push(Separator::new(SeparatorType::Related).widget_holder());
|
||||
|
||||
let pin_active = self.tool_data.pivot_gizmo.pin_active();
|
||||
let pin_enabled = self.tool_data.pivot_gizmo.pivot.old_pivot_position == ReferencePoint::None && !self.tool_data.pivot_gizmo.state.disabled;
|
||||
|
||||
if pin_active || pin_enabled {
|
||||
widgets.push(pin_pivot_widget(pin_active, pin_enabled, PivotToolSource::Select));
|
||||
}
|
||||
}
|
||||
|
||||
// Align
|
||||
let disabled = self.tool_data.selected_layers_count < 2;
|
||||
@@ -242,16 +272,46 @@ impl LayoutHolder for SelectTool {
|
||||
}
|
||||
}
|
||||
|
||||
#[message_handler_data]
|
||||
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for SelectTool {
|
||||
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
|
||||
if let ToolMessage::Select(SelectToolMessage::SelectOptions(SelectOptionsUpdate::NestedSelectionBehavior(nested_selection_behavior))) = message {
|
||||
self.tool_data.nested_selection_behavior = nested_selection_behavior;
|
||||
responses.add(ToolMessage::UpdateHints);
|
||||
let mut redraw_reference_pivot = false;
|
||||
|
||||
if let ToolMessage::Select(SelectToolMessage::SelectOptions(ref option_update)) = message {
|
||||
match option_update {
|
||||
SelectOptionsUpdate::NestedSelectionBehavior(nested_selection_behavior) => {
|
||||
self.tool_data.nested_selection_behavior = *nested_selection_behavior;
|
||||
responses.add(ToolMessage::UpdateHints);
|
||||
}
|
||||
SelectOptionsUpdate::PivotGizmoType(gizmo_type) => {
|
||||
if !self.tool_data.pivot_gizmo.state.disabled {
|
||||
self.tool_data.pivot_gizmo.state.gizmo_type = *gizmo_type;
|
||||
responses.add(ToolMessage::UpdateHints);
|
||||
let pivot_gizmo = self.tool_data.pivot_gizmo();
|
||||
responses.add(TransformLayerMessage::SetPivotGizmo { pivot_gizmo });
|
||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||
redraw_reference_pivot = true;
|
||||
}
|
||||
}
|
||||
SelectOptionsUpdate::TogglePivotGizmoType(state) => {
|
||||
self.tool_data.pivot_gizmo.state.disabled = !state;
|
||||
responses.add(ToolMessage::UpdateHints);
|
||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||
redraw_reference_pivot = true;
|
||||
}
|
||||
|
||||
SelectOptionsUpdate::TogglePivotPinned => {
|
||||
self.tool_data.pivot_gizmo.pivot.pinned = !self.tool_data.pivot_gizmo.pivot.pinned;
|
||||
responses.add(ToolMessage::UpdateHints);
|
||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||
redraw_reference_pivot = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.fsm_state.process_event(message, &mut self.tool_data, tool_data, &(), responses, false);
|
||||
|
||||
if self.tool_data.pivot.should_refresh_pivot_position() || self.tool_data.selected_layers_changed {
|
||||
if self.tool_data.pivot_gizmo.pivot.should_refresh_pivot_position() || self.tool_data.selected_layers_changed || redraw_reference_pivot {
|
||||
// Send the layout containing the updated pivot position (a bit ugly to do it here not in the fsm but that doesn't have SelectTool)
|
||||
self.send_layout(responses, LayoutTarget::ToolOptions);
|
||||
self.tool_data.selected_layers_changed = false;
|
||||
@@ -323,7 +383,8 @@ struct SelectToolData {
|
||||
drag_current: ViewportPosition,
|
||||
lasso_polygon: Vec<ViewportPosition>,
|
||||
selection_mode: Option<SelectionMode>,
|
||||
layers_dragging: Vec<LayerNodeIdentifier>,
|
||||
layers_dragging: Vec<LayerNodeIdentifier>, // Unordered, often used as temporary buffer
|
||||
ordered_layers: Vec<LayerNodeIdentifier>, // Ordered list of layers
|
||||
layer_selected_on_start: Option<LayerNodeIdentifier>,
|
||||
select_single_layer: Option<LayerNodeIdentifier>,
|
||||
axis_align: bool,
|
||||
@@ -331,7 +392,9 @@ struct SelectToolData {
|
||||
bounding_box_manager: Option<BoundingBoxManager>,
|
||||
snap_manager: SnapManager,
|
||||
cursor: MouseCursorIcon,
|
||||
pivot: Pivot,
|
||||
pivot_gizmo: PivotGizmo,
|
||||
pivot_gizmo_start: Option<DVec2>,
|
||||
pivot_gizmo_shift: Option<DVec2>,
|
||||
compass_rose: CompassRose,
|
||||
line_center: DVec2,
|
||||
skew_edge: EdgeBool,
|
||||
@@ -497,6 +560,24 @@ impl SelectToolData {
|
||||
responses.add(NodeGraphMessage::SendGraph);
|
||||
self.layers_dragging = original;
|
||||
}
|
||||
|
||||
fn state_from_pivot_gizmo(&self, mouse: DVec2) -> Option<SelectToolFsmState> {
|
||||
match self.pivot_gizmo.state.gizmo_type {
|
||||
PivotGizmoType::Pivot if self.pivot_gizmo.state.is_pivot() => self.pivot_gizmo.pivot.is_over(mouse).then_some(SelectToolFsmState::DraggingPivot),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn pivot_gizmo(&self) -> PivotGizmo {
|
||||
self.pivot_gizmo.clone()
|
||||
}
|
||||
|
||||
fn sync_history(&mut self, document: &DocumentMessageHandler) {
|
||||
let layers: Vec<_> = document.network_interface.selected_nodes().selected_visible_and_unlocked_layers(&document.network_interface).collect();
|
||||
self.ordered_layers.retain(|layer| layers.contains(layer));
|
||||
self.ordered_layers.extend(layers.iter().find(|&layer| !self.ordered_layers.contains(layer)));
|
||||
self.pivot_gizmo.layer = self.ordered_layers.last().copied()
|
||||
}
|
||||
}
|
||||
|
||||
impl Fsm for SelectToolFsmState {
|
||||
@@ -710,8 +791,63 @@ impl Fsm for SelectToolFsmState {
|
||||
.flatten()
|
||||
});
|
||||
|
||||
// Update pivot
|
||||
tool_data.pivot.update_pivot(document, &mut overlay_context, Some((angle,)));
|
||||
let mut active_origin = None;
|
||||
let mut origin_angle = 0.;
|
||||
if overlay_context.visibility_settings.origin() && !tool_data.pivot_gizmo.state.is_pivot_type() {
|
||||
let get_angle = |layer: LayerNodeIdentifier| -> f64 {
|
||||
let quad = Quad::from_box([DVec2::ZERO, DVec2::ONE]);
|
||||
let bounds = document.metadata().transform_to_viewport_with_first_transform_node_if_group(layer, &document.network_interface) * quad;
|
||||
(bounds.top_left() - bounds.top_right()).to_angle()
|
||||
};
|
||||
if tool_data.pivot_gizmo.state.gizmo_type == PivotGizmoType::Average {
|
||||
let mut count = 0_usize;
|
||||
|
||||
let sum: f64 = document
|
||||
.network_interface
|
||||
.selected_nodes()
|
||||
.selected_visible_and_unlocked_layers(&document.network_interface)
|
||||
.map(get_angle)
|
||||
.inspect(|_| count += 1)
|
||||
.sum();
|
||||
if count > 0 {
|
||||
origin_angle = sum / count as f64;
|
||||
}
|
||||
} else if tool_data.pivot_gizmo.state.gizmo_type == PivotGizmoType::Active {
|
||||
origin_angle = document
|
||||
.network_interface
|
||||
.selected_nodes()
|
||||
.selected_visible_and_unlocked_layers(&document.network_interface)
|
||||
.find(|&layer| Some(layer) == tool_data.pivot_gizmo.layer)
|
||||
.iter()
|
||||
.map(|&layer| get_angle(layer))
|
||||
.sum();
|
||||
}
|
||||
|
||||
for layer in document.network_interface.selected_nodes().selected_visible_and_unlocked_layers(&document.network_interface) {
|
||||
let origin = graph_modification_utils::get_viewport_origin(layer, &document.network_interface);
|
||||
if Some(layer) == tool_data.pivot_gizmo.layer {
|
||||
active_origin = Some(origin);
|
||||
continue;
|
||||
}
|
||||
overlay_context.dowel_pin(origin, origin_angle, None);
|
||||
}
|
||||
}
|
||||
if let Some(origin) = active_origin {
|
||||
overlay_context.dowel_pin(origin, origin_angle, Some(COLOR_OVERLAY_YELLOW));
|
||||
}
|
||||
|
||||
let has_layers = document.network_interface.selected_nodes().has_selected_nodes();
|
||||
let draw_pivot = tool_data.pivot_gizmo.state.is_pivot() && overlay_context.visibility_settings.pivot() && has_layers;
|
||||
tool_data.pivot_gizmo.pivot.recalculate_pivot(document);
|
||||
let pivot = draw_pivot.then_some(tool_data.pivot_gizmo.pivot.pivot).flatten();
|
||||
if let Some(pivot) = pivot {
|
||||
let offset = tool_data
|
||||
.pivot_gizmo_start
|
||||
.map(|offset| tool_data.pivot_gizmo.pivot_disconnected().then_some(tool_data.drag_current - offset).unwrap_or_default())
|
||||
.unwrap_or_default();
|
||||
let shift = tool_data.pivot_gizmo_shift.unwrap_or_default();
|
||||
overlay_context.pivot(pivot + offset + shift, angle);
|
||||
}
|
||||
|
||||
// Update compass rose
|
||||
if overlay_context.visibility_settings.compass_rose() {
|
||||
@@ -837,6 +973,15 @@ impl Fsm for SelectToolFsmState {
|
||||
(SelectionShapeType::Lasso, _) => overlay_context.polygon(polygon, None, fill_color),
|
||||
}
|
||||
}
|
||||
|
||||
if let Self::Dragging { .. } = self {
|
||||
let quad = Quad::from_box([tool_data.drag_start, tool_data.drag_current]);
|
||||
let document_start = document.metadata().document_to_viewport.inverse().transform_point2(quad.top_left());
|
||||
let document_current = document.metadata().document_to_viewport.inverse().transform_point2(quad.bottom_right());
|
||||
|
||||
overlay_context.translation_box(document_current - document_start, quad, None);
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
(_, SelectToolMessage::EditLayer) => {
|
||||
@@ -868,7 +1013,8 @@ impl Fsm for SelectToolFsmState {
|
||||
let intersection_list = document.click_list(input).collect::<Vec<_>>();
|
||||
let intersection = document.find_deepest(&intersection_list);
|
||||
|
||||
let (resize, rotate, skew) = transforming_transform_cage(document, &mut tool_data.bounding_box_manager, input, responses, &mut tool_data.layers_dragging);
|
||||
let position = tool_data.pivot_gizmo().position(document);
|
||||
let (resize, rotate, skew) = transforming_transform_cage(document, &mut tool_data.bounding_box_manager, input, responses, &mut tool_data.layers_dragging, Some(position));
|
||||
|
||||
// If the user is dragging the bounding box bounds, go into ResizingBounds mode.
|
||||
// If the user is dragging the rotate trigger, go into RotatingBounds mode.
|
||||
@@ -883,20 +1029,17 @@ impl Fsm for SelectToolFsmState {
|
||||
let angle = bounds.map_or(0., |quad| (quad.top_left() - quad.top_right()).to_angle());
|
||||
let mouse_position = input.mouse.position;
|
||||
let compass_rose_state = tool_data.compass_rose.compass_rose_state(mouse_position, angle);
|
||||
let is_over_pivot = tool_data.pivot.is_over(mouse_position);
|
||||
|
||||
let show_compass = bounds.is_some_and(|quad| quad.all_sides_at_least_width(COMPASS_ROSE_HOVER_RING_DIAMETER) && quad.contains(mouse_position));
|
||||
let can_grab_compass_rose = compass_rose_state.can_grab() && (show_compass || bounds.is_none());
|
||||
|
||||
let state = if is_over_pivot
|
||||
// Dragging the pivot
|
||||
{
|
||||
let state = if let Some(state) = tool_data.state_from_pivot_gizmo(input.mouse.position) {
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
|
||||
// tool_data.snap_manager.start_snap(document, input, document.bounding_boxes(), true, true);
|
||||
// tool_data.snap_manager.add_all_document_handles(document, input, &[], &[], &[]);
|
||||
|
||||
SelectToolFsmState::DraggingPivot
|
||||
state
|
||||
}
|
||||
// Dragging one (or two, forming a corner) of the transform cage bounding box edges
|
||||
else if resize {
|
||||
@@ -917,12 +1060,14 @@ impl Fsm for SelectToolFsmState {
|
||||
}
|
||||
|
||||
tool_data.layers_dragging = selected;
|
||||
|
||||
tool_data.get_snap_candidates(document, input);
|
||||
let (axis, using_compass) = {
|
||||
let axis_state = compass_rose_state.axis_type().filter(|_| can_grab_compass_rose);
|
||||
(axis_state.unwrap_or_default(), axis_state.is_some())
|
||||
};
|
||||
|
||||
tool_data.pivot_gizmo_start = Some(tool_data.drag_current);
|
||||
|
||||
SelectToolFsmState::Dragging {
|
||||
axis,
|
||||
using_compass,
|
||||
@@ -941,6 +1086,12 @@ impl Fsm for SelectToolFsmState {
|
||||
let extend = input.keyboard.key(extend_selection);
|
||||
if !extend && !input.keyboard.key(remove_from_selection) {
|
||||
responses.add(DocumentMessage::DeselectAllLayers);
|
||||
|
||||
if !tool_data.pivot_gizmo.pivot.pinned {
|
||||
let position = tool_data.pivot_gizmo.pivot.last_non_none_reference_point;
|
||||
responses.add(SelectToolMessage::SetPivot { position });
|
||||
}
|
||||
|
||||
tool_data.layers_dragging.clear();
|
||||
}
|
||||
|
||||
@@ -955,6 +1106,9 @@ impl Fsm for SelectToolFsmState {
|
||||
tool_data.get_snap_candidates(document, input);
|
||||
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
|
||||
tool_data.pivot_gizmo_start = Some(tool_data.drag_current);
|
||||
|
||||
SelectToolFsmState::Dragging {
|
||||
axis: Axis::None,
|
||||
using_compass: false,
|
||||
@@ -1098,7 +1252,10 @@ impl Fsm for SelectToolFsmState {
|
||||
(SelectToolFsmState::DraggingPivot, SelectToolMessage::PointerMove(modifier_keys)) => {
|
||||
let mouse_position = input.mouse.position;
|
||||
let snapped_mouse_position = mouse_position;
|
||||
tool_data.pivot.set_viewport_position(snapped_mouse_position, document, responses);
|
||||
|
||||
tool_data.pivot_gizmo.pivot.set_viewport_position(snapped_mouse_position);
|
||||
|
||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||
|
||||
// Auto-panning
|
||||
let messages = [
|
||||
@@ -1143,7 +1300,7 @@ impl Fsm for SelectToolFsmState {
|
||||
.map_or(MouseCursorIcon::Default, |bounds| bounds.get_cursor(input, true, dragging_bounds, Some(tool_data.skew_edge)));
|
||||
|
||||
// Dragging the pivot overrules the other operations
|
||||
if tool_data.pivot.is_over(input.mouse.position) {
|
||||
if tool_data.state_from_pivot_gizmo(input.mouse.position).is_some() {
|
||||
cursor = MouseCursorIcon::Move;
|
||||
}
|
||||
|
||||
@@ -1283,20 +1440,32 @@ impl Fsm for SelectToolFsmState {
|
||||
tool_data.snap_manager.cleanup(responses);
|
||||
tool_data.select_single_layer = None;
|
||||
|
||||
if let Some(start) = tool_data.pivot_gizmo_start {
|
||||
let offset = tool_data.pivot_gizmo.pivot_disconnected().then_some(tool_data.drag_current - start).unwrap_or_default();
|
||||
if let Some(v) = tool_data.pivot_gizmo.pivot.pivot.as_mut() {
|
||||
*v += offset;
|
||||
}
|
||||
}
|
||||
tool_data.pivot_gizmo_start = None;
|
||||
|
||||
let pivot_gizmo = tool_data.pivot_gizmo();
|
||||
responses.add(TransformLayerMessage::SetPivotGizmo { pivot_gizmo });
|
||||
|
||||
let selection = tool_data.nested_selection_behavior;
|
||||
SelectToolFsmState::Ready { selection }
|
||||
}
|
||||
(
|
||||
SelectToolFsmState::ResizingBounds
|
||||
| SelectToolFsmState::SkewingBounds { .. }
|
||||
| SelectToolFsmState::RotatingBounds
|
||||
| SelectToolFsmState::Dragging { .. }
|
||||
| SelectToolFsmState::DraggingPivot,
|
||||
SelectToolFsmState::ResizingBounds | SelectToolFsmState::SkewingBounds { .. } | SelectToolFsmState::RotatingBounds | SelectToolFsmState::DraggingPivot,
|
||||
SelectToolMessage::DragStop { .. } | SelectToolMessage::Enter,
|
||||
) => {
|
||||
let drag_too_small = input.mouse.position.distance(tool_data.drag_start) < 10. * f64::EPSILON;
|
||||
let response = if drag_too_small { DocumentMessage::AbortTransaction } else { DocumentMessage::EndTransaction };
|
||||
|
||||
let pivot_gizmo = tool_data.pivot_gizmo();
|
||||
responses.add(TransformLayerMessage::SetPivotGizmo { pivot_gizmo });
|
||||
|
||||
responses.add(response);
|
||||
|
||||
tool_data.axis_align = false;
|
||||
tool_data.snap_manager.cleanup(responses);
|
||||
|
||||
@@ -1432,8 +1601,48 @@ impl Fsm for SelectToolFsmState {
|
||||
(_, SelectToolMessage::SetPivot { position }) => {
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
|
||||
tool_data.pivot_gizmo.pivot.last_non_none_reference_point = position;
|
||||
tool_data.pivot_gizmo.pivot.pinned = false;
|
||||
|
||||
let pos: Option<DVec2> = position.into();
|
||||
tool_data.pivot.set_normalized_position(pos.unwrap(), document, responses);
|
||||
|
||||
tool_data.pivot_gizmo.pivot.set_normalized_position(pos.unwrap());
|
||||
|
||||
let pivot_gizmo = tool_data.pivot_gizmo();
|
||||
responses.add(TransformLayerMessage::SetPivotGizmo { pivot_gizmo });
|
||||
|
||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||
|
||||
self
|
||||
}
|
||||
(_, SelectToolMessage::SyncHistory) => {
|
||||
tool_data.sync_history(document);
|
||||
|
||||
self
|
||||
}
|
||||
(_, SelectToolMessage::ShiftSelectedNodes { offset }) => {
|
||||
let offset = document.metadata().document_to_viewport.transform_vector2(offset);
|
||||
if tool_data.pivot_gizmo.pivot_disconnected() {
|
||||
if let Some(v) = tool_data.pivot_gizmo.pivot.pivot.as_mut() {
|
||||
*v += offset;
|
||||
}
|
||||
|
||||
let pivot_gizmo = tool_data.pivot_gizmo();
|
||||
responses.add(TransformLayerMessage::SetPivotGizmo { pivot_gizmo });
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
(_, SelectToolMessage::PivotShift { offset, flush }) => {
|
||||
if flush {
|
||||
tool_data.pivot_gizmo.pivot.pivot.as_mut().map(|v| *v += tool_data.pivot_gizmo_shift.take().unwrap_or_default());
|
||||
let pivot_gizmo = tool_data.pivot_gizmo();
|
||||
responses.add(TransformLayerMessage::SetPivotGizmo { pivot_gizmo });
|
||||
return self;
|
||||
}
|
||||
if tool_data.pivot_gizmo.pivot_disconnected() {
|
||||
tool_data.pivot_gizmo_shift = offset;
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
@@ -1658,6 +1867,7 @@ fn drag_deepest_manipulation(responses: &mut VecDeque<Message>, selected: Vec<La
|
||||
.next()
|
||||
.expect("ROOT_PARENT should have a layer child when clicking"),
|
||||
);
|
||||
|
||||
if !remove {
|
||||
tool_data.layers_dragging.extend(vec![layer]);
|
||||
} else {
|
||||
|
||||
@@ -557,7 +557,7 @@ impl Fsm for ShapeToolFsmState {
|
||||
}
|
||||
}
|
||||
|
||||
let (resize, rotate, skew) = transforming_transform_cage(document, &mut tool_data.bounding_box_manager, input, responses, &mut tool_data.layers_dragging);
|
||||
let (resize, rotate, skew) = transforming_transform_cage(document, &mut tool_data.bounding_box_manager, input, responses, &mut tool_data.layers_dragging, None);
|
||||
|
||||
if !input.keyboard.key(Key::Control) {
|
||||
match (resize, rotate, skew) {
|
||||
|
||||
@@ -14,7 +14,7 @@ use graph_craft::document::{NodeId, NodeInput};
|
||||
use graphene_std::Color;
|
||||
use graphene_std::vector::{PointId, SegmentId, VectorModificationType};
|
||||
|
||||
#[derive(Default)]
|
||||
#[derive(Default, ExtractField)]
|
||||
pub struct SplineTool {
|
||||
fsm_state: SplineToolFsmState,
|
||||
tool_data: SplineToolData,
|
||||
@@ -123,6 +123,7 @@ impl LayoutHolder for SplineTool {
|
||||
}
|
||||
}
|
||||
|
||||
#[message_handler_data]
|
||||
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for SplineTool {
|
||||
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
|
||||
let ToolMessage::Spline(SplineToolMessage::UpdateOptions(action)) = message else {
|
||||
|
||||
@@ -9,7 +9,6 @@ use crate::messages::portfolio::document::utility_types::network_interface::Inpu
|
||||
use crate::messages::tool::common_functionality::auto_panning::AutoPanning;
|
||||
use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType};
|
||||
use crate::messages::tool::common_functionality::graph_modification_utils::{self, is_layer_fed_by_node_of_name};
|
||||
use crate::messages::tool::common_functionality::pivot::Pivot;
|
||||
use crate::messages::tool::common_functionality::resize::Resize;
|
||||
use crate::messages::tool::common_functionality::snapping::{self, SnapCandidatePoint, SnapData};
|
||||
use crate::messages::tool::common_functionality::transformation_cage::*;
|
||||
@@ -21,7 +20,7 @@ use graphene_std::renderer::Quad;
|
||||
use graphene_std::text::{Font, FontCache, TypesettingConfig, lines_clipping, load_font};
|
||||
use graphene_std::vector::style::Fill;
|
||||
|
||||
#[derive(Default)]
|
||||
#[derive(Default, ExtractField)]
|
||||
pub struct TextTool {
|
||||
fsm_state: TextToolFsmState,
|
||||
tool_data: TextToolData,
|
||||
@@ -171,6 +170,7 @@ impl LayoutHolder for TextTool {
|
||||
}
|
||||
}
|
||||
|
||||
#[message_handler_data]
|
||||
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for TextTool {
|
||||
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
|
||||
let ToolMessage::Text(TextToolMessage::UpdateOptions(action)) = message else {
|
||||
@@ -283,7 +283,6 @@ struct TextToolData {
|
||||
// Since the overlays must be drawn without knowledge of the inputs
|
||||
cached_resize_bounds: [DVec2; 2],
|
||||
bounding_box_manager: Option<BoundingBoxManager>,
|
||||
pivot: Pivot,
|
||||
snap_candidates: Vec<SnapCandidatePoint>,
|
||||
// TODO: Handle multiple layers in the future
|
||||
layer_dragging: Option<ResizingLayer>,
|
||||
@@ -526,7 +525,6 @@ impl Fsm for TextToolFsmState {
|
||||
}
|
||||
|
||||
bounding_box_manager.render_overlays(&mut overlay_context, false);
|
||||
tool_data.pivot.update_pivot(document, &mut overlay_context, None);
|
||||
}
|
||||
} else {
|
||||
tool_data.bounding_box_manager.take();
|
||||
|
||||
@@ -2,6 +2,7 @@ use crate::messages::input_mapper::utility_types::input_keyboard::Key;
|
||||
use crate::messages::portfolio::document::overlays::utility_types::OverlayContext;
|
||||
use crate::messages::portfolio::document::utility_types::transformation::TransformType;
|
||||
use crate::messages::prelude::*;
|
||||
use crate::messages::tool::common_functionality::pivot::PivotGizmo;
|
||||
use glam::DVec2;
|
||||
|
||||
#[impl_message(Message, ToolMessage, TransformLayer)]
|
||||
@@ -29,4 +30,5 @@ pub enum TransformLayerMessage {
|
||||
TypeDecimalPoint,
|
||||
TypeDigit { digit: u8 },
|
||||
TypeNegate,
|
||||
SetPivotGizmo { pivot_gizmo: PivotGizmo },
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ use crate::messages::portfolio::document::utility_types::document_metadata::Laye
|
||||
use crate::messages::portfolio::document::utility_types::misc::PTZ;
|
||||
use crate::messages::portfolio::document::utility_types::transformation::{Axis, OriginalTransforms, Selected, TransformOperation, TransformType, Typing};
|
||||
use crate::messages::prelude::*;
|
||||
use crate::messages::tool::common_functionality::pivot::{PivotGizmo, PivotGizmoType};
|
||||
use crate::messages::tool::common_functionality::shape_editor::ShapeState;
|
||||
use crate::messages::tool::tool_messages::tool_prelude::Key;
|
||||
use crate::messages::tool::utility_types::{ToolData, ToolType};
|
||||
@@ -20,7 +21,7 @@ const TRANSFORM_GRS_OVERLAY_PROVIDER: OverlayProvider = |context| TransformLayer
|
||||
const SLOW_KEY: Key = Key::Shift;
|
||||
const INCREMENTS_KEY: Key = Key::Control;
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
#[derive(Debug, Clone, Default, ExtractField)]
|
||||
pub struct TransformLayerMessageHandler {
|
||||
pub transform_operation: TransformOperation,
|
||||
|
||||
@@ -34,8 +35,11 @@ pub struct TransformLayerMessageHandler {
|
||||
start_mouse: ViewportPosition,
|
||||
|
||||
original_transforms: OriginalTransforms,
|
||||
pivot_gizmo: PivotGizmo,
|
||||
pivot: ViewportPosition,
|
||||
|
||||
path_bounds: Option<[DVec2; 2]>,
|
||||
|
||||
local_pivot: DocumentPosition,
|
||||
local_mouse_start: DocumentPosition,
|
||||
grab_target: DocumentPosition,
|
||||
@@ -61,27 +65,64 @@ impl TransformLayerMessageHandler {
|
||||
}
|
||||
}
|
||||
|
||||
fn calculate_pivot(selected_points: &Vec<&ManipulatorPointId>, vector_data: &VectorData, viewspace: DAffine2, get_location: impl Fn(&ManipulatorPointId) -> Option<DVec2>) -> Option<(DVec2, DVec2)> {
|
||||
fn calculate_pivot(
|
||||
document: &DocumentMessageHandler,
|
||||
selected_points: &Vec<&ManipulatorPointId>,
|
||||
vector_data: &VectorData,
|
||||
viewspace: DAffine2,
|
||||
get_location: impl Fn(&ManipulatorPointId) -> Option<DVec2>,
|
||||
gizmo: &mut PivotGizmo,
|
||||
) -> (Option<(DVec2, DVec2)>, Option<[DVec2; 2]>) {
|
||||
let average_position = || {
|
||||
let mut point_count = 0_usize;
|
||||
selected_points.iter().filter_map(|p| get_location(p)).inspect(|_| point_count += 1).sum::<DVec2>() / point_count as f64
|
||||
};
|
||||
let bounds = selected_points.iter().filter_map(|p| get_location(p)).fold(None, |acc: Option<[DVec2; 2]>, point| {
|
||||
if let Some([mut min, mut max]) = acc {
|
||||
min.x = min.x.min(point.x);
|
||||
min.y = min.y.min(point.y);
|
||||
max.x = max.x.max(point.x);
|
||||
max.y = max.y.max(point.y);
|
||||
Some([min, max])
|
||||
} else {
|
||||
Some([point, point])
|
||||
}
|
||||
});
|
||||
gizmo.pivot.recalculate_pivot_for_layer(document, bounds);
|
||||
let position = || {
|
||||
(if !gizmo.state.disabled {
|
||||
match gizmo.state.gizmo_type {
|
||||
PivotGizmoType::Average => None,
|
||||
PivotGizmoType::Active => gizmo.point.and_then(|p| get_location(&p)),
|
||||
PivotGizmoType::Pivot => gizmo.pivot.pivot,
|
||||
}
|
||||
} else {
|
||||
None
|
||||
})
|
||||
.unwrap_or_else(average_position)
|
||||
};
|
||||
let [point] = selected_points.as_slice() else {
|
||||
// Handle the case where there are multiple points
|
||||
let mut point_count = 0;
|
||||
let average_position = selected_points.iter().filter_map(|p| get_location(p)).inspect(|_| point_count += 1).sum::<DVec2>() / point_count as f64;
|
||||
|
||||
return Some((average_position, average_position));
|
||||
let position = position();
|
||||
return (Some((position, position)), bounds);
|
||||
};
|
||||
|
||||
match point {
|
||||
ManipulatorPointId::PrimaryHandle(_) | ManipulatorPointId::EndHandle(_) => {
|
||||
// Get the anchor position and transform it to the pivot
|
||||
let pivot_pos = point.get_anchor_position(vector_data).map(|anchor_position| viewspace.transform_point2(anchor_position))?;
|
||||
let target = viewspace.transform_point2(point.get_position(vector_data)?);
|
||||
Some((pivot_pos, target))
|
||||
let (Some(pivot_position), Some(position)) = (
|
||||
point.get_anchor_position(vector_data).map(|anchor_position| viewspace.transform_point2(anchor_position)),
|
||||
point.get_position(vector_data),
|
||||
) else {
|
||||
return (None, None);
|
||||
};
|
||||
let target = viewspace.transform_point2(position);
|
||||
(Some((pivot_position, target)), None)
|
||||
}
|
||||
_ => {
|
||||
// Calculate the average position of all selected points
|
||||
let mut point_count = 0;
|
||||
let average_position = selected_points.iter().filter_map(|p| get_location(p)).inspect(|_| point_count += 1).sum::<DVec2>() / point_count as f64;
|
||||
Some((average_position, average_position))
|
||||
let position = position();
|
||||
(Some((position, position)), bounds)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -134,6 +175,26 @@ fn update_colinear_handles(selected_layers: &[LayerNodeIdentifier], document: &D
|
||||
}
|
||||
|
||||
type TransformData<'a> = (&'a DocumentMessageHandler, &'a InputPreprocessorMessageHandler, &'a ToolData, &'a mut ShapeState);
|
||||
|
||||
pub fn custom_data() -> MessageData {
|
||||
MessageData::new(
|
||||
String::from("TransformData<'a>"),
|
||||
// TODO: When <https://github.com/dtolnay/proc-macro2/issues/503> is resolved and released,
|
||||
// TODO: use <https://doc.rust-lang.org/stable/proc_macro/struct.Span.html#method.line> to get
|
||||
// TODO: the line number instead of hardcoding it to the magic number on the following lines
|
||||
// TODO: which points to the line of the `type TransformData<'a> = ...` definition above.
|
||||
// TODO: Also, utilize the line number in the actual output, since it is currently unused.
|
||||
vec![
|
||||
(String::from("&'a DocumentMessageHandler"), 177),
|
||||
(String::from("&'a InputPreprocessorMessageHandler"), 177),
|
||||
(String::from("&'a ToolData"), 177),
|
||||
(String::from("&'a mut ShapeState"), 177),
|
||||
],
|
||||
file!(),
|
||||
)
|
||||
}
|
||||
|
||||
#[message_handler_data(CustomData)]
|
||||
impl MessageHandler<TransformLayerMessage, TransformData<'_>> for TransformLayerMessageHandler {
|
||||
fn process_message(&mut self, message: TransformLayerMessage, responses: &mut VecDeque<Message>, (document, input, tool_data, shape_editor): TransformData) {
|
||||
let using_path_tool = tool_data.active_tool_type == ToolType::Path;
|
||||
@@ -177,18 +238,17 @@ impl MessageHandler<TransformLayerMessage, TransformData<'_>> for TransformLayer
|
||||
}
|
||||
|
||||
if !using_path_tool {
|
||||
*selected.pivot = selected.mean_average_of_pivots();
|
||||
self.pivot_gizmo.recalculate_transform(document);
|
||||
*selected.pivot = self.pivot_gizmo.position(document);
|
||||
self.local_pivot = document.metadata().document_to_viewport.inverse().transform_point2(*selected.pivot);
|
||||
self.grab_target = document.metadata().document_to_viewport.inverse().transform_point2(selected.mean_average_of_pivots());
|
||||
self.grab_target = self.local_pivot;
|
||||
}
|
||||
// Here vector data from all layers is not considered which can be a problem in pivot calculation
|
||||
else if let Some(vector_data) = selected_layers.first().and_then(|&layer| document.network_interface.compute_modified_vector(layer)) {
|
||||
*selected.original_transforms = OriginalTransforms::default();
|
||||
|
||||
let viewspace = document.metadata().transform_to_viewport(selected_layers[0]);
|
||||
|
||||
let selected_segments = shape_editor.selected_segments().collect::<HashSet<_>>();
|
||||
|
||||
let mut affected_points = shape_editor.selected_points().copied().collect::<Vec<_>>();
|
||||
|
||||
for (segment_id, _, start, end) in vector_data.segment_bezier_iter() {
|
||||
@@ -201,8 +261,16 @@ impl MessageHandler<TransformLayerMessage, TransformData<'_>> for TransformLayer
|
||||
let affected_point_refs = affected_points.iter().collect();
|
||||
|
||||
let get_location = |point: &&ManipulatorPointId| point.get_position(&vector_data).map(|position| viewspace.transform_point2(position));
|
||||
if let Some((new_pivot, grab_target)) = calculate_pivot(&affected_point_refs, &vector_data, viewspace, |point: &ManipulatorPointId| get_location(&point)) {
|
||||
if let (Some((new_pivot, grab_target)), bounds) = calculate_pivot(
|
||||
document,
|
||||
&affected_point_refs,
|
||||
&vector_data,
|
||||
viewspace,
|
||||
|point: &ManipulatorPointId| get_location(&point),
|
||||
&mut self.pivot_gizmo,
|
||||
) {
|
||||
*selected.pivot = new_pivot;
|
||||
self.path_bounds = bounds;
|
||||
|
||||
self.local_pivot = document_to_viewport.inverse().transform_point2(*selected.pivot);
|
||||
self.grab_target = document_to_viewport.inverse().transform_point2(grab_target);
|
||||
@@ -228,116 +296,93 @@ impl MessageHandler<TransformLayerMessage, TransformData<'_>> for TransformLayer
|
||||
return;
|
||||
}
|
||||
|
||||
for layer in document.metadata().all_layers() {
|
||||
if !document.network_interface.is_artboard(&layer.to_node(), &[]) {
|
||||
continue;
|
||||
};
|
||||
let viewport_box = input.viewport_bounds.size();
|
||||
let axis_constraint = self.transform_operation.axis_constraint();
|
||||
|
||||
let viewport_box = input.viewport_bounds.size();
|
||||
let axis_constraint = self.transform_operation.axis_constraint();
|
||||
let format_rounded = |value: f64, precision: usize| {
|
||||
if self.typing.digits.is_empty() || !self.transform_operation.can_begin_typing() {
|
||||
format!("{:.*}", precision, value).trim_end_matches('0').trim_end_matches('.').to_string()
|
||||
} else {
|
||||
self.typing.string.clone()
|
||||
}
|
||||
};
|
||||
|
||||
let format_rounded = |value: f64, precision: usize| {
|
||||
if self.typing.digits.is_empty() || !self.transform_operation.can_begin_typing() {
|
||||
format!("{:.*}", precision, value).trim_end_matches('0').trim_end_matches('.').to_string()
|
||||
// TODO: Ensure removing this and adding this doesn't change the position of layers under PTZ ops
|
||||
// responses.add(TransformLayerMessage::PointerMove {
|
||||
// slow_key: SLOW_KEY,
|
||||
// increments_key: INCREMENTS_KEY,
|
||||
// });
|
||||
|
||||
match self.transform_operation {
|
||||
TransformOperation::None => (),
|
||||
TransformOperation::Grabbing(translation) => {
|
||||
let translation = translation.to_dvec(self.initial_transform, self.increments);
|
||||
let viewport_translate = document_to_viewport.transform_vector2(translation);
|
||||
let pivot = document_to_viewport.transform_point2(self.grab_target);
|
||||
let quad = Quad::from_box([pivot, pivot + viewport_translate]);
|
||||
|
||||
responses.add(SelectToolMessage::PivotShift {
|
||||
offset: Some(viewport_translate),
|
||||
flush: false,
|
||||
});
|
||||
|
||||
let typed_string = (!self.typing.digits.is_empty() && self.transform_operation.can_begin_typing()).then(|| self.typing.string.clone());
|
||||
overlay_context.translation_box(translation, quad, typed_string);
|
||||
}
|
||||
TransformOperation::Scaling(scale) => {
|
||||
let scale = scale.to_f64(self.increments);
|
||||
let text = format!("{}x", format_rounded(scale, 3));
|
||||
let pivot = document_to_viewport.transform_point2(self.local_pivot);
|
||||
let start_mouse = document_to_viewport.transform_point2(self.local_mouse_start);
|
||||
let local_edge = start_mouse - pivot;
|
||||
let local_edge = project_edge_to_quad(local_edge, &self.layer_bounding_box, self.local, axis_constraint);
|
||||
let boundary_point = pivot + local_edge * scale.min(1.);
|
||||
let end_point = pivot + local_edge * scale.max(1.);
|
||||
|
||||
if scale > 0. {
|
||||
overlay_context.dashed_line(pivot, boundary_point, None, None, Some(2.), Some(2.), Some(0.5));
|
||||
}
|
||||
overlay_context.line(boundary_point, end_point, None, None);
|
||||
|
||||
let transform = DAffine2::from_translation(boundary_point.midpoint(pivot) + local_edge.perp().normalize_or(DVec2::X) * local_edge.element_product().signum() * 24.);
|
||||
overlay_context.text(&text, COLOR_OVERLAY_BLUE, None, transform, 16., [Pivot::Middle, Pivot::Middle]);
|
||||
}
|
||||
TransformOperation::Rotating(rotation) => {
|
||||
let angle = rotation.to_f64(self.increments);
|
||||
let pivot = document_to_viewport.transform_point2(self.local_pivot);
|
||||
let start_mouse = document_to_viewport.transform_point2(self.local_mouse_start);
|
||||
let offset_angle = if self.grs_pen_handle {
|
||||
self.handle - self.last_point
|
||||
} else if using_path_tool {
|
||||
start_mouse - pivot
|
||||
} else {
|
||||
self.typing.string.clone()
|
||||
}
|
||||
};
|
||||
|
||||
// TODO: Ensure removing this and adding this doesn't change the position of layers under PTZ ops
|
||||
// responses.add(TransformLayerMessage::PointerMove {
|
||||
// slow_key: SLOW_KEY,
|
||||
// increments_key: INCREMENTS_KEY,
|
||||
// });
|
||||
|
||||
match self.transform_operation {
|
||||
TransformOperation::None => (),
|
||||
TransformOperation::Grabbing(translation) => {
|
||||
let translation = translation.to_dvec(self.initial_transform, self.increments);
|
||||
let viewport_translate = document_to_viewport.transform_vector2(translation);
|
||||
let pivot = document_to_viewport.transform_point2(self.grab_target);
|
||||
let quad = Quad::from_box([pivot, pivot + viewport_translate]).0;
|
||||
let e1 = (self.layer_bounding_box.0[1] - self.layer_bounding_box.0[0]).normalize_or(DVec2::X);
|
||||
|
||||
if matches!(axis_constraint, Axis::Both | Axis::X) && translation.x != 0. {
|
||||
let end = if self.local { (quad[1] - quad[0]).rotate(e1) + quad[0] } else { quad[1] };
|
||||
overlay_context.dashed_line(quad[0], end, None, None, Some(2.), Some(2.), Some(0.5));
|
||||
|
||||
let x_transform = DAffine2::from_translation((quad[0] + end) / 2.);
|
||||
overlay_context.text(&format_rounded(translation.x, 3), COLOR_OVERLAY_BLUE, None, x_transform, 4., [Pivot::Middle, Pivot::End]);
|
||||
}
|
||||
|
||||
if matches!(axis_constraint, Axis::Both | Axis::Y) && translation.y != 0. {
|
||||
let end = if self.local { (quad[3] - quad[0]).rotate(e1) + quad[0] } else { quad[3] };
|
||||
overlay_context.dashed_line(quad[0], end, None, None, Some(2.), Some(2.), Some(0.5));
|
||||
let x_parameter = viewport_translate.x.clamp(-1., 1.);
|
||||
let y_transform = DAffine2::from_translation((quad[0] + end) / 2. + x_parameter * DVec2::X * 0.);
|
||||
let pivot_selection = if x_parameter >= -1e-3 { Pivot::Start } else { Pivot::End };
|
||||
if axis_constraint != Axis::Both || self.typing.digits.is_empty() || !self.transform_operation.can_begin_typing() {
|
||||
overlay_context.text(&format_rounded(translation.y, 2), COLOR_OVERLAY_BLUE, None, y_transform, 3., [pivot_selection, Pivot::Middle]);
|
||||
}
|
||||
}
|
||||
|
||||
if matches!(axis_constraint, Axis::Both) && translation.x != 0. && translation.y != 0. {
|
||||
overlay_context.line(quad[1], quad[2], None, None);
|
||||
overlay_context.line(quad[3], quad[2], None, None);
|
||||
}
|
||||
}
|
||||
TransformOperation::Scaling(scale) => {
|
||||
let scale = scale.to_f64(self.increments);
|
||||
let text = format!("{}x", format_rounded(scale, 3));
|
||||
let pivot = document_to_viewport.transform_point2(self.local_pivot);
|
||||
let start_mouse = document_to_viewport.transform_point2(self.local_mouse_start);
|
||||
let local_edge = start_mouse - pivot;
|
||||
let local_edge = project_edge_to_quad(local_edge, &self.layer_bounding_box, self.local, axis_constraint);
|
||||
let boundary_point = pivot + local_edge * scale.min(1.);
|
||||
let end_point = pivot + local_edge * scale.max(1.);
|
||||
|
||||
if scale > 0. {
|
||||
overlay_context.dashed_line(pivot, boundary_point, None, None, Some(2.), Some(2.), Some(0.5));
|
||||
}
|
||||
overlay_context.line(boundary_point, end_point, None, None);
|
||||
|
||||
let transform = DAffine2::from_translation(boundary_point.midpoint(pivot) + local_edge.perp().normalize_or(DVec2::X) * local_edge.element_product().signum() * 24.);
|
||||
overlay_context.text(&text, COLOR_OVERLAY_BLUE, None, transform, 16., [Pivot::Middle, Pivot::Middle]);
|
||||
}
|
||||
TransformOperation::Rotating(rotation) => {
|
||||
let angle = rotation.to_f64(self.increments);
|
||||
let pivot = document_to_viewport.transform_point2(self.local_pivot);
|
||||
let start_mouse = document_to_viewport.transform_point2(self.local_mouse_start);
|
||||
let offset_angle = if self.grs_pen_handle {
|
||||
self.handle - self.last_point
|
||||
} else if using_path_tool {
|
||||
start_mouse - pivot
|
||||
} else {
|
||||
self.layer_bounding_box.top_right() - self.layer_bounding_box.top_right()
|
||||
};
|
||||
let tilt_offset = document.document_ptz.unmodified_tilt();
|
||||
let offset_angle = offset_angle.to_angle() + tilt_offset;
|
||||
let width = viewport_box.max_element();
|
||||
let radius = start_mouse.distance(pivot);
|
||||
let arc_radius = ANGLE_MEASURE_RADIUS_FACTOR * width;
|
||||
let radius = radius.clamp(ARC_MEASURE_RADIUS_FACTOR_RANGE.0 * width, ARC_MEASURE_RADIUS_FACTOR_RANGE.1 * width);
|
||||
let angle_in_degrees = angle.to_degrees();
|
||||
let display_angle = if angle_in_degrees.is_sign_positive() {
|
||||
angle_in_degrees - (angle_in_degrees / 360.).floor() * 360.
|
||||
} else if angle_in_degrees.is_sign_negative() {
|
||||
angle_in_degrees - ((angle_in_degrees / 360.).floor() + 1.) * 360.
|
||||
} else {
|
||||
angle_in_degrees
|
||||
};
|
||||
let text = format!("{}°", format_rounded(display_angle, 2));
|
||||
let text_texture_width = overlay_context.get_width(&text) / 2.;
|
||||
let text_texture_height = 12.;
|
||||
let text_angle_on_unit_circle = DVec2::from_angle((angle % TAU) / 2. + offset_angle);
|
||||
let text_texture_position = DVec2::new(
|
||||
(arc_radius + 4. + text_texture_width) * text_angle_on_unit_circle.x,
|
||||
(arc_radius + text_texture_height) * text_angle_on_unit_circle.y,
|
||||
);
|
||||
let transform = DAffine2::from_translation(text_texture_position + pivot);
|
||||
overlay_context.draw_angle(pivot, radius, arc_radius, offset_angle, angle);
|
||||
overlay_context.text(&text, COLOR_OVERLAY_BLUE, None, transform, 16., [Pivot::Middle, Pivot::Middle]);
|
||||
}
|
||||
self.layer_bounding_box.top_right() - self.layer_bounding_box.top_right()
|
||||
};
|
||||
let tilt_offset = document.document_ptz.unmodified_tilt();
|
||||
let offset_angle = offset_angle.to_angle() + tilt_offset;
|
||||
let width = viewport_box.max_element();
|
||||
let radius = start_mouse.distance(pivot);
|
||||
let arc_radius = ANGLE_MEASURE_RADIUS_FACTOR * width;
|
||||
let radius = radius.clamp(ARC_MEASURE_RADIUS_FACTOR_RANGE.0 * width, ARC_MEASURE_RADIUS_FACTOR_RANGE.1 * width);
|
||||
let angle_in_degrees = angle.to_degrees();
|
||||
let display_angle = if angle_in_degrees.is_sign_positive() {
|
||||
angle_in_degrees - (angle_in_degrees / 360.).floor() * 360.
|
||||
} else if angle_in_degrees.is_sign_negative() {
|
||||
angle_in_degrees - ((angle_in_degrees / 360.).floor() + 1.) * 360.
|
||||
} else {
|
||||
angle_in_degrees
|
||||
};
|
||||
let text = format!("{}°", format_rounded(display_angle, 2));
|
||||
let text_texture_width = overlay_context.get_width(&text) / 2.;
|
||||
let text_texture_height = 12.;
|
||||
let text_angle_on_unit_circle = DVec2::from_angle((angle % TAU) / 2. + offset_angle);
|
||||
let text_texture_position = DVec2::new(
|
||||
(arc_radius + 4. + text_texture_width) * text_angle_on_unit_circle.x,
|
||||
(arc_radius + text_texture_height) * text_angle_on_unit_circle.y,
|
||||
);
|
||||
let transform = DAffine2::from_translation(text_texture_position + pivot);
|
||||
overlay_context.draw_angle(pivot, radius, arc_radius, offset_angle, angle);
|
||||
overlay_context.text(&text, COLOR_OVERLAY_BLUE, None, transform, 16., [Pivot::Middle, Pivot::Middle]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -364,6 +409,8 @@ impl MessageHandler<TransformLayerMessage, TransformData<'_>> for TransformLayer
|
||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||
}
|
||||
|
||||
responses.add(SelectToolMessage::PivotShift { offset: None, flush: true });
|
||||
|
||||
if final_transform {
|
||||
responses.add(OverlaysMessage::RemoveProvider(TRANSFORM_GRS_OVERLAY_PROVIDER));
|
||||
}
|
||||
@@ -487,6 +534,7 @@ impl MessageHandler<TransformLayerMessage, TransformData<'_>> for TransformLayer
|
||||
responses.add(ToolMessage::UpdateHints);
|
||||
}
|
||||
|
||||
responses.add(SelectToolMessage::PivotShift { offset: None, flush: false });
|
||||
responses.add(OverlaysMessage::RemoveProvider(TRANSFORM_GRS_OVERLAY_PROVIDER));
|
||||
}
|
||||
TransformLayerMessage::ConstrainX => {
|
||||
@@ -694,6 +742,9 @@ impl MessageHandler<TransformLayerMessage, TransformData<'_>> for TransformLayer
|
||||
self.initial_transform,
|
||||
)
|
||||
}
|
||||
TransformLayerMessage::SetPivotGizmo { pivot_gizmo } => {
|
||||
self.pivot_gizmo = pivot_gizmo;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ use graphene_std::text::FontCache;
|
||||
use std::borrow::Cow;
|
||||
use std::fmt::{self, Debug};
|
||||
|
||||
#[derive(ExtractField)]
|
||||
pub struct ToolActionHandlerData<'a> {
|
||||
pub document: &'a mut DocumentMessageHandler,
|
||||
pub document_id: DocumentId,
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
use crate::messages::prelude::*;
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
#[derive(Debug, Clone, Default, ExtractField)]
|
||||
pub struct WorkspaceMessageHandler {
|
||||
node_graph_visible: bool,
|
||||
}
|
||||
|
||||
#[message_handler_data]
|
||||
impl MessageHandler<WorkspaceMessage, ()> for WorkspaceMessageHandler {
|
||||
fn process_message(&mut self, message: WorkspaceMessage, _responses: &mut VecDeque<Message>, _data: ()) {
|
||||
match message {
|
||||
|
||||
@@ -413,6 +413,7 @@ mod test {
|
||||
use super::*;
|
||||
use crate::messages::portfolio::document::utility_types::network_interface::NodeNetworkInterface;
|
||||
use crate::test_utils::test_prelude::{self, NodeGraphLayer};
|
||||
use graph_craft::ProtoNodeIdentifier;
|
||||
use graph_craft::document::NodeNetwork;
|
||||
use graphene_std::Context;
|
||||
use graphene_std::NodeInputDecleration;
|
||||
@@ -422,7 +423,7 @@ mod test {
|
||||
/// Stores all of the monitor nodes that have been attached to a graph
|
||||
#[derive(Default)]
|
||||
pub struct Instrumented {
|
||||
protonodes_by_name: HashMap<String, Vec<Vec<Vec<NodeId>>>>,
|
||||
protonodes_by_name: HashMap<ProtoNodeIdentifier, Vec<Vec<Vec<NodeId>>>>,
|
||||
protonodes_by_path: HashMap<Vec<NodeId>, Vec<Vec<NodeId>>>,
|
||||
}
|
||||
|
||||
@@ -449,7 +450,7 @@ mod test {
|
||||
}
|
||||
if let DocumentNodeImplementation::ProtoNode(identifier) = &mut node.implementation {
|
||||
path.push(*id);
|
||||
self.protonodes_by_name.entry(identifier.name.to_string()).or_default().push(monitor_node_ids.clone());
|
||||
self.protonodes_by_name.entry(identifier.clone()).or_default().push(monitor_node_ids.clone());
|
||||
self.protonodes_by_path.insert(path.clone(), monitor_node_ids);
|
||||
path.pop();
|
||||
}
|
||||
@@ -457,7 +458,7 @@ mod test {
|
||||
for (input, monitor_id) in monitor_nodes {
|
||||
let monitor_node = DocumentNode {
|
||||
inputs: vec![input],
|
||||
implementation: DocumentNodeImplementation::proto("graphene_core::memo::MonitorNode"),
|
||||
implementation: DocumentNodeImplementation::ProtoNode(graphene_std::memo::monitor::IDENTIFIER),
|
||||
manual_composition: Some(graph_craft::generic!(T)),
|
||||
skip_deduplication: true,
|
||||
..Default::default()
|
||||
@@ -495,7 +496,7 @@ mod test {
|
||||
Input::Result: Send + Sync + Clone + 'static,
|
||||
{
|
||||
self.protonodes_by_name
|
||||
.get(Input::identifier())
|
||||
.get(&Input::identifier())
|
||||
.map_or([].as_slice(), |x| x.as_slice())
|
||||
.iter()
|
||||
.filter_map(|inputs| inputs.get(Input::INDEX))
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
use super::*;
|
||||
use crate::messages::frontend::utility_types::{ExportBounds, FileType};
|
||||
use glam::{DAffine2, DVec2};
|
||||
use graph_craft::concrete;
|
||||
use graph_craft::document::value::TaggedValue;
|
||||
use graph_craft::document::{NodeId, NodeNetwork};
|
||||
use graph_craft::graphene_compiler::Compiler;
|
||||
use graph_craft::proto::GraphErrors;
|
||||
use graph_craft::wasm_application_io::EditorPreferences;
|
||||
use graph_craft::{ProtoNodeIdentifier, concrete};
|
||||
use graphene_std::Context;
|
||||
use graphene_std::application_io::{NodeGraphUpdateMessage, NodeGraphUpdateSender, RenderConfig};
|
||||
use graphene_std::instances::Instance;
|
||||
@@ -46,7 +46,7 @@ pub struct NodeRuntime {
|
||||
inspect_state: Option<InspectState>,
|
||||
|
||||
/// Mapping of the fully-qualified node paths to their preprocessor substitutions.
|
||||
substitutions: HashMap<String, DocumentNode>,
|
||||
substitutions: HashMap<ProtoNodeIdentifier, DocumentNode>,
|
||||
|
||||
// TODO: Remove, it doesn't need to be persisted anymore
|
||||
/// The current renders of the thumbnails for layer nodes.
|
||||
@@ -435,7 +435,7 @@ impl InspectState {
|
||||
|
||||
let monitor_node = DocumentNode {
|
||||
inputs: vec![NodeInput::node(inspect_node, 0)], // Connect to the primary output of the inspect node
|
||||
implementation: DocumentNodeImplementation::proto("graphene_core::memo::MonitorNode"),
|
||||
implementation: DocumentNodeImplementation::ProtoNode(graphene_std::memo::monitor::IDENTIFIER),
|
||||
manual_composition: Some(graph_craft::generic!(T)),
|
||||
skip_deduplication: true,
|
||||
..Default::default()
|
||||
|
||||
@@ -45,3 +45,19 @@ pub trait TransitiveChild: Into<Self::Parent> + Into<Self::TopParent> {
|
||||
pub trait Hint {
|
||||
fn hints(&self) -> HashMap<String, String>;
|
||||
}
|
||||
|
||||
pub trait HierarchicalTree {
|
||||
fn build_message_tree() -> DebugMessageTree;
|
||||
|
||||
fn message_handler_data_str() -> MessageData {
|
||||
MessageData::new(String::new(), Vec::new(), "")
|
||||
}
|
||||
|
||||
fn message_handler_str() -> MessageData {
|
||||
MessageData::new(String::new(), Vec::new(), "")
|
||||
}
|
||||
|
||||
fn path() -> &'static str {
|
||||
""
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
#[derive(Debug)]
|
||||
pub struct MessageData {
|
||||
name: String,
|
||||
fields: Vec<(String, usize)>,
|
||||
path: &'static str,
|
||||
}
|
||||
|
||||
impl MessageData {
|
||||
pub fn new(name: String, fields: Vec<(String, usize)>, path: &'static str) -> MessageData {
|
||||
MessageData { name, fields, path }
|
||||
}
|
||||
|
||||
pub fn name(&self) -> &str {
|
||||
&self.name
|
||||
}
|
||||
|
||||
pub fn fields(&self) -> &Vec<(String, usize)> {
|
||||
&self.fields
|
||||
}
|
||||
|
||||
pub fn path(&self) -> &'static str {
|
||||
self.path
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct DebugMessageTree {
|
||||
name: String,
|
||||
variants: Option<Vec<DebugMessageTree>>,
|
||||
message_handler: Option<MessageData>,
|
||||
message_handler_data: Option<MessageData>,
|
||||
path: &'static str,
|
||||
}
|
||||
|
||||
impl DebugMessageTree {
|
||||
pub fn new(name: &str) -> DebugMessageTree {
|
||||
DebugMessageTree {
|
||||
name: name.to_string(),
|
||||
variants: None,
|
||||
message_handler: None,
|
||||
message_handler_data: None,
|
||||
path: "",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_path(&mut self, path: &'static str) {
|
||||
self.path = path;
|
||||
}
|
||||
|
||||
pub fn add_variant(&mut self, variant: DebugMessageTree) {
|
||||
if let Some(variants) = &mut self.variants {
|
||||
variants.push(variant);
|
||||
} else {
|
||||
self.variants = Some(vec![variant]);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_message_handler_data_field(&mut self, message_handler_data: MessageData) {
|
||||
self.message_handler_data = Some(message_handler_data);
|
||||
}
|
||||
|
||||
pub fn add_message_handler_field(&mut self, message_handler: MessageData) {
|
||||
self.message_handler = Some(message_handler);
|
||||
}
|
||||
|
||||
pub fn name(&self) -> &str {
|
||||
&self.name
|
||||
}
|
||||
|
||||
pub fn path(&self) -> &'static str {
|
||||
self.path
|
||||
}
|
||||
|
||||
pub fn variants(&self) -> Option<&Vec<DebugMessageTree>> {
|
||||
self.variants.as_ref()
|
||||
}
|
||||
|
||||
pub fn message_handler_data_fields(&self) -> Option<&MessageData> {
|
||||
self.message_handler_data.as_ref()
|
||||
}
|
||||
|
||||
pub fn message_handler_fields(&self) -> Option<&MessageData> {
|
||||
self.message_handler.as_ref()
|
||||
}
|
||||
|
||||
pub fn has_message_handler_data_fields(&self) -> bool {
|
||||
match self.message_handler_data_fields() {
|
||||
Some(_) => true,
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn has_message_handler_fields(&self) -> bool {
|
||||
match self.message_handler_fields() {
|
||||
Some(_) => true,
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user